React Hook Form으로 회원가입/로그인 구현하기

·2024년 1월 9일
1

TIL series

목록 보기
21/28
post-thumbnail

❓ React-Hook-Form을 적용한 이유

지금까지는 input 이벤트를 관리할 때 useState & onChange를 이용했다. 이렇게 했을 때 단점은 input 창에 변화가 있을 때마다 리렌더링이 발생한다는 것이다. 입력의 개수가 적을 때는 크게 문제가 되지 않겠지만, 관리해야 하는 state가 많아지고, 입력할 때마다 컴포넌트가 리렌더링하게 되면 불필요한 연산이 발생하고, 유효성 검사 같은 기능이 필요할 경우 코드 양이 많아지고, 유지보수에 어려움이 생길 수 있다.

🌷 React-Hook-Form의 장점 🌷

Performance of React Hook Form
Performance is one of the primary reasons why this library was created.

"성능이 곧 react-hook-form을 만든 이유"

1. 성능 최적화

내부적으로 성능 최적화를 고려해 설계되었다. 리렌더링을 최소화 해 불필요한 작업을 방지하고, 빠른 사용자 경험을 제공한다.
📌 대규모 폼이나 복잡한 유효성 검사 로직을 가진 폼에서 특히 유용!!

2. 간편한 API

사용하기 쉽고 직관적인 API를 제공한다.

3. 유효성 검사 용이

다양한 유효성 검사 규칙과 커스텀 유효성 검사 규칙을 사용해 폼의 요구사항을 쉽게 처리할 수 있다.



🎯 React-Hook-Form 사용하기

🌊 react-hook-form 없이 구현한 회원가입

const SignUp = ({ login, setLogin, setOpen }: Props) => {
  //입력받은 이메일, 패스워드, 닉네임
  const [id, setId] = useState<string>("");
  const [pw, setPw] = useState<string>("");
  const [pwCheck, setPwCheck] = useState<string>("");
  const [name, setName] = useState<string>("");
  
  //유효성 검사에 사용
  const pwValid = pwCheck !== "" && pw === pwCheck ? true : false;
  const isValid = pwValid && emailValidChk(id) && pw.length > 5 && name !== "" ? false : true;
  
  ...
  
  return (
    ...
   <form className="space-y-6" onSubmit={signUpNewUser}>
            <div>
              <label htmlFor="email" className="block text-sm font-medium leading-6 text-gray-900">
                Email address{" "}
                {emailValidChk(id) ? (
                  <span></span>
                ) : (
                  <span className="text-xs text-red-600"> 이메일 형식이 유효하지 않습니다.</span>
                )}
              </label>
              <div className="mt-2">
                <input
                  id="email"
                  name="email"
                  type="email"
                  required
                  className="block w-full rounded-md border-0 p-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                  onChange={(e) => setId(e.target.value)}
                  placeholder="  example@where2go.com"
                />
              </div>
            </div>
             
			//비밀번호 input 생략

            <div>
              <div className="flex items-center justify-between">
                <label htmlFor="pwConfirm" className="block text-sm font-medium leading-6 text-gray-900">
                  Password 확인{" "}
                  {!pwValid ? (
                    <span className="text-xs text-red-600">비밀번호가 일치하지 않습니다.</span>
                  ) : (
                    <span></span>
                  )}
                </label>
              </div>
              <div className="mt-2">
                <input
                  id="pwConfirm"
                  name="pwConfirm"
                  type="password"
                  required
                  className="block w-full rounded-md border-0 p-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                  onChange={(e) => setPwCheck(e.target.value)}
                  placeholder="  비밀번호 재입력"
                />
              </div>
            </div>

            // username 입력 생략

            <div>
              <Button type="submit" size="md" disabled={isValid} className="sm:mx-auto sm:w-full sm:max-w-sm mb-8 mt-2">
                SignUp & Login
              </Button>
            </div>
          </form>
			...
  );
  

react-hook-form 없이 만든 회원가입은 input마다 useState로 처리하고, 유효성 검사도 로직을 각각 만들어 에러 메세지 처리를 해야한다. onChange로 state를 변경할 때마다 렌더링 되므로 불필요한 렌더링이 너무 많고, 유효성 검사가 복잡하다는 단점이 한눈에 보인다.

✨ React-hook-form으로 리팩토링

⚙ React-hook-form install

npm install react-hook-form

...
import { useForm, SubmitHandler } from "react-hook-form";
...

interface FormValue {
  id: string;
  pw: string;
  pwCheck: string;
  name: string;
}

const SignUp = ({ mode, setMode }: Props) => {
  //회원가입 함수 signUpNewUser() 생략

  const {
    register,
    handleSubmit,
    watch,
    formState: { errors },
    setError
  } = useForm<FormValue>({ mode: "onBlur" });

  //비밀번호 유효성 검사를 위해 pw 입력값 확인
  const passwordRef = useRef<string | null>(null);
  passwordRef.current = watch("pw");

  const onSubmit: SubmitHandler<FormValue> = (inputData) => {
    console.log(inputData);
    signUpNewUser(inputData.id, inputData.pw, inputData.name);
  };
  

return (
  <>
   		...
        <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
          <div>
            <label htmlFor="email" className="block text-sm font-medium leading-6 text-gray-900">
              Email address
              <span className="text-xs text-red-600">{errors?.id?.message}</span>
            </label>
            <div>
              <input
                id="email"
                type="email"
                {...register("id", {
                  required: "   이메일을 입력하세요.",
                  pattern: {
                    value: /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-za-z0-9\-]+/,
                    message: "   이메일 형식이 유효하지 않습니다."
                  }
                })}
                placeholder="example@yolocean.com"
                className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-300 sm:text-sm sm:leading-6"
              />
            </div>
          </div>

          <div>
            <div className="flex items-center justify-between">
              <label htmlFor="password" className="block text-sm font-medium leading-6 text-gray-900">
                Password
                <span className="text-xs text-red-600">{errors?.pw?.message}</span>
              </label>
            </div>
            <div>
              <input
                id="password"
                type="password"
                {...register("pw", {
                  required: "   비밀번호를 입력하세요.",
                  minLength: {
                    value: 6,
                    message: "   6자리 이상 입력하세요."
                  }
                })}
                placeholder="비밀번호를 입력하세요."
                className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-300 sm:text-sm sm:leading-6"
              />
            </div>
          </div>

          <div>
            <div>
              <label htmlFor="pwConfirm" className="block text-sm font-medium leading-6 text-gray-900">
                Password 확인 <span className="text-xs text-red-600">{errors?.pwCheck?.message}</span>
              </label>
            </div>
            <div>
              <input
                id="pwConfirm"
                type="password"
                {...register("pwCheck", {
                  required: true,
                  validate: (value) => (value === passwordRef.current ? true : "   비밀번호가 일치하지 않습니다.")
                })}
                placeholder="비밀번호 확인"
                className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-300 sm:text-sm sm:leading-6"
              />
            </div>
          </div>

          <div>
            <div>
              <label htmlFor="name" className="block text-sm font-medium leading-6 text-gray-900">
                Username
                <span className="text-xs text-red-600">{errors?.name?.message}</span>
              </label>
            </div>
            <div>
              <input
                id="name"
                type="name"
                placeholder="yolocean에서 사용할 이름을 입력하세요"
                {...register("name", {
                  required: "   이름을 입력하세요"
                })}
                className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-300 sm:text-sm sm:leading-6"
              />
            </div>
          </div>

          <div>
            <button
              type="submit"
              // disabled={isValid}
              className="flex w-full justify-center rounded-md bg-blue-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 cursor-pointer"
            >
              SignUp & Login
            </button>
          </div>
        </form>

        <div>
          <p
            onClick={() => setMode(!mode)}
            className="mt-10 text-center text-sm text-blue-700 hover:text-blue-500 cursor-pointer"
          >
            로그인 하기
          </p>
        </div>
      </div>
    </div>
  </>
);
};

...

register

이메일 input 부분을 살펴보면

 <input
     id="email"
     type="email"
     {...register("id", {
           required: "   이메일을 입력하세요.",
           pattern: {
           		value: /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-za-z0-9\-]+/,
              	message: "   이메일 형식이 유효하지 않습니다."
           }
      })}
      placeholder="example@yolocean.com"
      className="..."
 />
  • 첫번째 매개변수 name : 해당 필드를 다룰 key값으로 반드시 들어가야 하는 값이다.
  • 두번째 매개변수 options 객체: 유효성 검사를 위한 프로퍼티들이 들어갈 수 있다.
  • required, min, max, minLength, maxLength, pattern 등...
  • value와 message로 구성된 객체를 주면 해당 에러에 대한 구체적인 메세지를 제공할 수도 있다.

watch

코드 상단 비밀번호 유효성 검사 부분에서

//비밀번호 유효성 검사를 위해 pw 입력값 확인
  const passwordRef = useRef<string | null>(null);
  passwordRef.current = watch("pw");
  • watch를 폼에 입력된 값을 구독해 실시간으로 체크할 수 있게 한다,
  • 매개변수를 주지 않으면 전체 값을 관찰할 수 있고, 매개변수를 주면 해당 값만 관찰할 수 있다.

handleSubmit

폼에서 데이터를 입력하고 사용자가 등록(회원가입) 버튼을 누르면 submit 이벤트가 발생한다. 이때, 사용자가 해당 데이터를 올바른 형식으로 입력했는지 검증을 한다. form 태그의 onSubmit 에 handleSubmit 이라는 함수를 넣어주고 매개변수로 우리가 정의한 onSubmit 함수를 넣어주면 된다. onSubmit 함수를 정의 할 때 매개변수로 data 라는 값을 받을 수 있는데, 해당 값은 사용자가 제출 버튼을 클릭 한 후 내려오는 사용자 입장에서 최종으로 제출하는 데이터가 된다. 이 값을 이용해 서버에 값을 넘겨주면 된다.

0개의 댓글