로그인 같은 폼을 만들 때 자주 사용한 react-hook-form에서 제공한 useForm에 대해 정리 해보았다.

 

사용하는 이유

  • 유효성 검사를 쉽게 할 수 있는, 성능이 우수하고 유연하며 확장 가능한 form을 제공하는 라이브러리이다
  • 비제어 컴포넌트 방식으로 구현되어있기에 렌더링 이슈를 해결하면서도, form의 데이터와 상태를 Provider 아래라면 어느 곳에서든지 props drilling 없이 사용할 수 있다
  • 코드 단순화 및 유지보수 비용을 낮춘다

 

제어 컴포넌트와 비제어 컴포넌트

  • 제어 컴포넌트

HTML에서<input>,<textarea>,<select> 와 같은 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트합니다. React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트됩니다.

폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어합니다. 이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트 (controlled component)“라고 합니다. 즉 state가 렌더링을 제어하는 것을 제어 컴포넌트라고 하는데 onChange 방식이 제어 컴포넌트라고 할 수 있다.

관련 공식 링크 : https://ko.legacy.reactjs.org/docs/forms.html#controlled-components

  • 비제어 컴포넌트

제어 컴포넌트에서 폼 데이터는 React 컴포넌트에서 다루어진다. 대안인 비제어 컴포넌트는 DOM 자체에서 폼 데이터가 다루어다.

모든 state 업데이트에 대한 이벤트 핸들러를 작성하는 대신 비제어 컴포넌트를 만들려면 ref를 사용하여 DOM에서 폼 값을 가져올 수 있다.

ref는 값을 업데이트 하여도 리랜더링 되지 않는 특성으로, 입력이 모두 되고난 후 ref를 통해 값을 한번에 가져와서 활용한다. state로 값을 관리하지 않기 때문에 값이 바뀔 때마다 리랜더링을 하지 않고 값을 한번에 가져올 수 있는 성능 상 이점이 있으나, 데이터를 완벽하게 가져올 수 없는 단점이 있다.

즉 만약 a와 b라는 컴포넌트가 있을 때, a에 대한 변화를 즉각적으로 b가 영향을 받아야 할때 비제어 컴포넌트는 이런 방식에 대한 대응을 할 수 없다.

관련 공식 링크 : https://ko.legacy.reactjs.org/docs/uncontrolled-components.html

 

react-hook-form 사용하기

공식 홈

 

useForm

Performant, flexible and extensible forms with easy-to-use validation.

react-hook-form.com

 

설치

npm install react-hook-form

 

 

사용법

주요 속성

const { register, handleSubmit, watch, formState: { errors } } = useForm();

 

 

defaultValue

useForm에 defaultValues을 넘겨주면 해당 값으로 초기화된 값을 받아온다.

useForm({
  defaultValues: {
    firstName: '',
    lastName: ''
  }
})

 

 

register

상태를 변경하는 데 사용하는 함수로 onChange, onBlur, ref, name을 가지고 있다.

<input {...register("email")} />

두 번째 매개변수에 조건을 줄 수 있다.

<input {...register("email", {
	 /* option */
})} />
  • required : 필수 값 지정, true 일 경우 값이 없다면 error를 받는다.
  • maxLength & minLength : 글자 수 제한 적용
<input
   type="password"
   placeholder="Password"
   {...register("password", {
       required: "비밀번호는 필수 입력입니다.",
       minLength: {
       value: 4,
       message: "4자리 이상 비밀번호를 입력하세요.",
     },
   })}
/>
  • min & max : 숫자 값 적용
<input
  type="number"
  {...register("item", {
    required: "itme 갯수는 필수 값입니다.",
    valueAsNumber: true,
    min: {
      value: 1,
      message: "1개 이상이어야 합니다."
    },
    max: {
      value: 10,
      message: "10개 이어야 합니다."
    }
  })}
/>;
  • pattern : 정규식 적용
<input
  {...register("email", {
    required: "이메일은 필수 입력입니다.",
    pattern: {
      value: /\S+@\S+\.\S+/,
      message: "이메일 형식에 맞지 않습니다."
    }
  })}
  type="text"
  placeholder="Email"
/>;
  • validate : 커스텀 validation
<input
	type="email"
  placeholder="email"
  {...register("email", {
    required: "email은 필수 값 입니다.",
    validate: {
      domainCheck: email =>
        email.split("@")[1] === "gmail.com" || "gmail만 가능합니다."
    }
  })}
/>;

 

 

handleSubmit

submit 이벤트를 할당해주는 함수로 기본적으로 e.preventDefault()를 가지고 있기 때문에 작성하지 않아도 된다. handleSubmit()의 첫 번째 인자는 성공했을 때 실행시키는 함수를 받고, 두 번째 인자는 실패했을 때 실행시키는 함수를 받는다.

const onValid = async (data: loginFormType) => {
    const result = await signIn("credentials", {
      redirect: false,
      email: data.email,
      password: data.password,
    });
    if (result?.error) {
     setError("loginError", {
        message: "이메일 주소와 비밀번호를 다시 확인해주세요.",
      });
    }
};

<form onSubmit={handleSubmit(onValid)}>
	//...
</form>

 

 

watch

form에서 입력된 값을 구독하여 실시간으로 체크할 수 있게 해주는 함수이다.

const id = watch("id");

 

formState

에러에 대한 정보는 formState 객체의 errors에 담겨있다.

{errors.email && (
     <span className="errorTxt">{errors.email.message}</span>
)}

formState에는 errors 외에도 다양한 속성이 있다.

  • submitCount : submit 한 횟수
  • dirtyFields : 기본 값이 수정된 필드들이 담김 - defaultValues가 있어야만 사용 가능
  • touchedFields : 사용자에 의해 수정된 필드들이 담겨져 있음
  • isValid : 에러가 있는지 알 수 있음
  • isSubmitting : 양식이 현재 제출 중인 상태인지 아닌 지를 알아낼 수 있음

<button type="submit" disabled={isSubmitting}>

disabled 속성에 이 isSubmitting 값을 설정해 주면 로그인 버튼이 양식의 제출 처리가 끝날 때까지 비활성화가 된다.

 

 

useForm을 적용한 로그인 페이지

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

interface loginFormType {
  email: string;
  password: string;
  loginError?: string;
}

export default function Login() {
	  const {
    register,
    handleSubmit,
    setError,
    formState: { errors, isSubmitting },
	  } = useForm<loginFormType>();

  const onValid = async (data: loginFormType) => {
    const result = await signIn("credentials", {
      redirect: false,
      email: data.email,
      password: data.password,
    });
    if (result?.error) {
     setError("loginError", {
        message: "이메일 주소와 비밀번호를 다시 확인해주세요.",
      });
    }
  };

  return (
    <div css={loginPage}>
      <div>
        <h1>
          PoledCS
          <br /> 관리자 로그인
        </h1>
        <form onSubmit={handleSubmit(onValid)} css={loginForm}>
          <div className="input_area">
            <div>
              <input
                type="text"
                placeholder="Email"
                {...register("email", {
                  required: "이메일은 필수 입력입니다.",
                  pattern: {
                    value: /\S+@\S+\.\S+/,
                    message: "이메일 형식에 맞지 않습니다.",
                  },
                })}
                onChange={onChange}
              />
              {errors.email && (
                <span className="errorTxt">{errors.email.message}</span>
              )}
            </div>
            <div>
              <input
                type="password"
                placeholder="Password"
                {...register("password", {
                  required: "비밀번호는 필수 입력입니다.",
                  minLength: {
                    value: 4,
                    message: "4자리 이상 비밀번호를 입력하세요.",
                  },
                })}
              />
              {errors.password && (
                <span className="errorTxt">{errors.password.message}</span>
              )}
            </div>
            <p {...register("loginError")}>
              {errors.loginError && (
                <span className="errorLogin">{errors.loginError.message}</span>
              )}
            </p>
          </div>
          <button type="submit" disabled={isSubmitting}>
            로그인
          </button>
        </form>
      </div>
    </div>
  );
}

 

 

참고

 

React Hook Form 라이브러리 사용법

Engineering Blog by Dale Seo

www.daleseo.com

 

 

'React' 카테고리의 다른 글

react checkbox 전체 선택  (0) 2024.09.03
리스트 중 하나 선택  (0) 2024.09.03
React Query(Tanstack Query)  (0) 2024.08.30
눈 내리는 효과  (0) 2024.08.28
Pagination  (0) 2024.08.26
minsun309