๐ŸŠ ๋‚‘๊นกํŒœ_06 : React Hook Form์„ ์ด์šฉํ•œ ํผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

Boriยท2022๋…„ 1์›” 30์ผ
8

Project_Kkingkkang.farm

๋ชฉ๋ก ๋ณด๊ธฐ
6/9
post-thumbnail

์ง„ํ–‰ ์ƒํ™ฉ

login-email.tsx

  • API ์—ฐ๋™
  • ์ด๋ฉ”์ผ, ๋น„๋ฐ€๋ฒˆํ˜ธ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
  • ์ด๋ฉ”์ผ, ๋น„๋ฐ€๋ฒˆํ˜ธ ์ผ์น˜ ์—ฌ๋ถ€ ํ™•์ธ
  • ๋ชจ๋‘ ํ†ต๊ณผ ํ›„ local Storage์— token ๊ฐ’ ์ €์žฅ ์—ฌ๋ถ€ ํ™•์ธ

๐Ÿ“Ž React Hook Form

  • ๊ฐ„๋‹จํ•˜๊ฒŒ ํผ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•  ์ˆ˜ ์žˆ๋Š” ํผ ๊ฒ€์ฆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๐Ÿ”ธ ์„ค์น˜

npm install react-hook-form
# yarn์œผ๋กœ ์„ค์น˜ํ•  ๊ฒฝ์šฐ
yarn add react-hook-form

๐Ÿ”ธ useForm

  • useForm์€ ํผ์„ ์‰ฝ๊ฒŒ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ custom hook
// useForm์˜ ๋ชจ๋“  ์ธ์ˆ˜์™€ ๊ธฐ๋ณธ๊ฐ’
type FormInputs = {
  firstName: string;
  lastName: string;
};

const { register } = useForm<FormInputs>({
  mode: 'onSubmit',
  reValidateMode: 'onChange',
  defaultValues: {},
  resolver: undefined,
  context: undefined,
  criteriaMode: "firstError",
  shouldFocusError: true,
  shouldUnregister: true,
})

// ์œ„ ์ฝ”๋“œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑ ๊ฐ€๋Šฅ
const { register } = useForm<{ firstName: string; lastName: string; }>({
  mode: 'onSubmit',
  reValidateMode: 'onChange',
  defaultValues: {},
  resolver: undefined,
  context: undefined,
  criteriaMode: "firstError",
  shouldFocusError: true,
  shouldUnregister: true,
})

mode: onChange | onBlur | onSubmit | onTouched | all = 'onSubmit'

  • onSubmit(default) : string | submit ์ด๋ฒคํŠธ๊ฐ€ ์ผ์–ด๋‚  ๋•Œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ์œ ํšจํ•˜์ง€ ์•Š์€ ์ž…๋ ฅ์€ onChange ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ ์œ ํšจ์„ฑ์„ ๋‹ค์‹œ ํ™•์ธ
  • onChange : string | input์˜ value๊ฐ€ ๋ณ€ํ•  ๋•Œ๋งˆ๋‹ค ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ์—ฌ๋Ÿฌ๋ฒˆ ๋ฆฌ๋žœ๋”๋ง (๋žœ๋”๋ง ์„ฑ๋Šฅ์„ ๋–จ์–ด๋œจ๋ฆฌ๋ฏ€๋กœ ์ถ”์ฒœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.)

register: (name: string, RegisterOptions?) => ({ onChange, onBlur, name, ref})

register ๋ฉ”์„œ๋“œ๋Š” input์ด๋‚˜ select ์š”์†Œ์— ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ทœ์น™์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ref : React.RefObject | React element ref
  <input name="test" ref={register} />
  • required : boolean | ํ•„์ˆ˜ ์š”์†Œ๋ฅผ ์ง€์ •ํ•  ๋•Œ, true๋กœ ์ง€์ •
<input
  name="test"
  ref={
    register({
      required: true
    })
  }
/>
  • maxLength, minLength : number | ์ž…๋ ฅ์— ํ—ˆ์šฉ๋˜๋Š” ์ตœ๋Œ€/์ตœ์†Œ ๊ธธ์ด
<input
  name="test"
  ref={
    register({
      maxLength: 2
    })
  }
/>
  • pattern : RegExp | ์ •๊ทœ์‹ ํŒจํ„ด์„ ์ด์šฉํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ฐ€๋Šฅ
<input
  name="test"
  ref={
    register({
      pattern: /[A-Za-z]{3}/
    })
  }
/>

handleSubmit: ((data: Object, e?: Event) => void, (errors: Object, e?: Event) => void) => Function

handleSubmit ํ•จ์ˆ˜๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ–ˆ์„ ๋•Œ ํผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋Š”๋‹ค.
handleSubmit ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋น„๋™๊ธฐ๋กœ ํผ์„ submit ํ•  ์ˆ˜ ์žˆ๋‹ค.

// It can be invoked remotely as well
handleSubmit(onSubmit)();

// You can pass an async function for asynchronous validation.
handleSubmit(async (data) => await fetchAPI(data))
  • SubmitHandler : (data: Object, e?: Event) => void | A successful callback
  • SubmitErrorHandler : (errors: Object, e?: Event) => void | An error callback.

๐Ÿ”ธ formState

  • formState๋Š” ๋ง ๊ทธ๋Œ€๋กœ ํผ์˜ ์ƒํƒœ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋‹ค.
  • formState is wrapped with a Proxy to improve render performance and skip extra logic if specific state is not subscribed to. Therefore make sure you invoke or read it before a render in order to enable the state update.
    => ์˜์—ญํ•ด์„œ ์ž‘์„ฑํ•ด๋ณด๋ ค๊ณ  ํ–ˆ์œผ๋‚˜ ์‹คํŒจ
import React from "react";
import { useForm } from "react-hook-form";

export default function App () {
  const {
    register,
    handleSubmit,
    formState
  } = useForm();

  const onSubmit = (data) => console.log(data);

  React.useEffect(() => {
    console.log("touchedFields", formState.touchedFields);
  },[formState]); // use entire formState object as optional array arg in useEffect, not individual properties of it

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("test")} />
      <input type="submit" />
    </form>
  );
};
  • isValid : boolean | ํผ์— ์—๋Ÿฌ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ true
    isValid๋Š” useForm mode๊ฐ€ onChange, onTouched, onBlur์ผ ๊ฒฝ์šฐ์• ๋งŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • errors : object | ErrorMessage ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด์šฉํ•˜๋ฉด ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์‰ฝ๊ฒŒ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ”ธ ์ด๋ฉ”์ผ๋กœ ๋กœ๊ทธ์ธํ•˜๋Š” ํŽ˜์ด์ง€ ์ฝ”๋“œ

import { useForm } from 'react-hook-form';

const LoginEmail: NextPage = () => {
  const router = useRouter();
  useEffect(() => {
    if (localStorage.getItem('token')) {
      router.push('/');
    }
  }, []);

  const regExpEm =
    /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i;
  const regExgPw = /^[A-Za-z0-9]{6,12}$/;

  const [loginError, setLoginError] = useState<boolean>(false);
  const {
    register,
    handleSubmit,
    formState: { errors, isValid },
  } = useForm<{ email: string; password: string }>({ mode: 'onChange' });

  const onSubmit = handleSubmit(async (data) => {
    const res = await axios(`${API_ENDPOINT}/user/login`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      data: JSON.stringify({
        user: {
          email: data.email,
          password: data.password,
        },
      }),
    });
    if (res.data.message) {
      setLoginError(true);
    } else {
      localStorage.setItem('account', res.data.user.accountname);
      localStorage.setItem('token', res.data.user.token);
      router.push('/');
    }
  });

  return (
    <>
      <Head>
        <title>๋กœ๊ทธ์ธใ…ฃ๋‚‘๊นกํŒœ</title>
      </Head>
      <MainLoginEmail>
        <TitleMain>๋กœ๊ทธ์ธ</TitleMain>
        <FormLogin onSubmit={onSubmit}>
          <BoxInp>
            <Label htmlFor="userEmail">
              ์ด๋ฉ”์ผ
              <Input
                type="email"
                id="userEmail"
                required
                {...register('email', { required: true, pattern: regExpEm })}
              />
            </Label>
          </BoxInp>
          {errors?.email?.type === 'required' && (
            <TxtError>* ์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”</TxtError>
          )}
          {errors?.email?.type === 'pattern' && (
            <TxtError>* ์ž˜๋ชป๋œ ์ด๋ฉ”์ผ ํ˜•์‹์ž…๋‹ˆ๋‹ค.</TxtError>
          )}
          <BoxInp>
            <Label htmlFor="userPw">
              ๋น„๋ฐ€๋ฒˆํ˜ธ
              <Input
                type="password"
                id="userPw"
                required
                {...register('password', { required: true, pattern: regExgPw })}
              />
            </Label>
            {errors?.password?.type === 'pattern' && (
              <TxtError>
                * ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์˜๋ฌธ(๋Œ€/์†Œ๋ฌธ์ž ๊ตฌ๋ถ„), ์ˆซ์ž ์กฐํ•ฉํ•˜์—ฌ 6~12์ž๋ฆฌ๋กœ
                ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.
              </TxtError>
            )}
            {loginError && (
              <TxtError>* ์ด๋ฉ”์ผ ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.</TxtError>
            )}
          </BoxInp>
          <BtnLogin type="submit" disabled={!isValid}>
            ๋กœ๊ทธ์ธ
          </BtnLogin>
        </FormLogin>
        <Link href="/signup" passHref>
          <LinkSignUp>์ด๋ฉ”์ผ๋กœ ํšŒ์›๊ฐ€์ž…</LinkSignUp>
        </Link>
      </MainLoginEmail>
    </>
  );
};

์ฐธ๊ณ ๋งํฌ

  • React Hook Form
    => React Hook Form - KR
    => ๋ชจ๋“  ํŽ˜์ด์ง€๊ฐ€ ํ•œ๊ตญ์–ด๋กœ ๋ฒˆ์—ญ๋œ ๊ฒƒ์€ ์•„๋‹ˆ์ง€๋งŒ ํ•œ๊ตญ์–ด๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ ์ž์ฒด์— ๋งŒ์กฑ!

6๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2022๋…„ 1์›” 30์ผ

์ตœ๊ณ ..

1๊ฐœ์˜ ๋‹ต๊ธ€
comment-user-thumbnail
2022๋…„ 1์›” 31์ผ

๋„˜ ๋ฉ‹์ ธ์š”~

1๊ฐœ์˜ ๋‹ต๊ธ€
comment-user-thumbnail
2023๋…„ 1์›” 3์ผ

๊ตฌ๊ธ€๋งํ•˜๋‹ค๊ฐ€ ๋“ค์–ด์™”๋Š”๋ฐ boriguri ๋‹˜์ด์—ˆ๋”ฐ... ๐Ÿงก

1๊ฐœ์˜ ๋‹ต๊ธ€