React-Hook-Form을 이용해 유효성 검사 해보기

후이·2025년 7월 24일
0

React

목록 보기
1/2
post-thumbnail

리액트로 회원가입 페이지 제작을 하면서 유효성 검사를 어떻게 할 지 고민하다
React-Hook-Form 이라는 라이브러리를 발견해서 이를 사용해보기로 했습니다.
백엔드 처리는 하지 않고 화면만 개발했습니다.

0️⃣ React-Hook-Form

라이브러리 사용을 위해 npm을 통해 설치해주겠습니다.

npm install react-hook-form

useForm()

라이브러리를 설치했다면 React-Hook-Form을 사용해보겠습니다.
useForm() 훅을 이용해서 Form 기능을 관리하는 값들을 가져옵니다.

const {
  register,
  formState: { errors },
  handleSubmit,
  watch,
  getValues,
} = useForm({ mode: 'onBlur' });

해당 훅을 통해 반환 된 값들을 구조 분해 할당을 통해 담아주었습니다.

  • register
    form의 입력 요소들을 React-Hook-Form에 등록하고, 유효성 검사 조건을 설정하는 함수입니다.
  • formState: { errors }
    formState 객체에서 errors 속성을 가져오고 errors 변수에 할당합니다. 유효성 검사 결과가 실패라면, errors의 값이 true로, 성공이라면, false가 됩니다. 다른 속성들이 더 있지만 errors만 사용했습니다.
  • handleSubmit
    해당 함수를 통해 form을 제출할 때 동작할 함수를 전달합니다. form의 유효성 검사가 자동으로 수행되며, 검사에 실패했을경우 제출을 하지 않습니다.
  • watch
    form의 입력 값을 감시합니다. watch('registerName')로 해당하는 필드의 값의 변경을 감시하거나 watch(['registerName1', 'registerName2'])로 여러 개의 값을 감시할 수 있습니다.
  • getValues
    해당 함수를 통해 필드의 값을 가져올 수 있습니다. getValues('registerName')
  • useForm({mode: ''})
    훅 안에 mode값을 설정할 수 있습니다.
    빈 칸일 때에는 기본적으로 form이 제출되어야 유효성 검사가 동작합니다.
    저는 입력 필드에서 'onBlur'가 되면 유효성 검사를 수행하도록 했지만 'onChange'로 바꾼다면 입력될 때마다 유효성 검사가 가능합니다.

1️⃣ Form 만들기

이메일

저는 간단한 회원가입 페이지라 이메일, 닉네임, 비밀번호, 비밀번호 확인만 만들었습니다.
우선 이메일을 먼저 만들어보겠습니다.

...
<div id="email-box" className="email-box">
  <label htmlFor="email" className="basic-p">
    이메일
  </label>
  <input
    className={`input-box email ${errors.email ? 'auth-err-border' : ''}`}
    name="email"
    type="email"
    placeholder="이메일을 입력해주세요"
    {...register('email', 
                 { required: true, 
                   pattern:
                      /^[a-zA-Z0-9+-\_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/,
                 })}
    {errors.email && (<p className="auth-err-msg">잘못된 이메일입니다.</p>)}
  />
</div>
...

register 부분 부터 보겠습니다.
함수의 첫 번째 매개변수로 이름을 등록해줍니다. 그리고 두 번째 인자로는 유효성 검사 조건들을 설정하게 됩니다. 유효성 검사 옵션들을 좀 살펴보자면,

  • required: 필수
  • maxLength, minLength: 최대 길이, 최소 길이
  • max, min: 최댓값, 최솟값
  • pattern: 정규식
  • validate: 사용자 정의 함수

가 있습니다. required: true 옵션 지정하여 필수 요소로 만들고 pattern을 이메일 정규식으로 설정했습니다.
className 속성은 유효성 검사에 실패 했을 때 빨간색 테두리로 바꾸기 위해 errors.email을 통해 해당하는 필드의 유효성 검사 여부를 확인했습니다. 이제 이메일 입력 필드에서 다른 곳을 클릭하면 유효성 검사 결과 스타일이 적용됩니다.
validate_email

닉네임

...
<div id="nickname-box" className="nickname-box">
  <label htmlFor="nickname" className="basic-p">
    닉네임
  </label>
  <input
    name="nickname"
    className={`input-box nickname ${errors.nickname ? 'auth-err-border' : ''}`}
    type="text"
    placeholder="닉네임을 입력해주세요"
    {...register('nickname', {
      required: true,
      minLength: 2,
      maxLength: 10,
    })}
  />
</div>
{errors.nickname?.type === 'required' && (<p className="auth-err-msg">닉네임을 입력해주세요</p>)}
{errors.nickname?.type === 'maxLength' && 
  (<p className="auth-err-msg">닉네임이 너무 깁니다.</p>)}
...

닉네임의 유효성 검사 부분은 유효성 검사 부분에 최소 길이와 최대 길이를 설정해주었습니다. 그리고 에러 메시지를 표출해줄건데 닉네임이 없을 때와 닉네임이 입력 길이를 초과했을 때 두 개의 에러를 하나만 표출해주기 위해 errors.nickname?.type 으로 유효성 검사 실패 조건이 어떤 것인지 가져와 maxLength일 경우 를 추가 조건으로 넣어주었습니다.

옵셔널 체이닝을 사용하지 않으면 errors.nicknameundefined 상태일 때 type 속성에 접근 하려고 하여 에러가 발생합니다.
blank_nicknameover_nickname

비밀번호

비밀번호 입력 칸에는 비밀번호 보이기 / 가리기 기능을 넣기 위해 눈 모양 아이콘을 추가 했습니다.

<div id="pw-box" className="pw-box">
  <label htmlFor="pw" className="basic-p pw-label">
    비밀번호
  </label>
  <input
    className={`input-box pw ${errors.password ? 'auth-err-border' : ''}`}
    name="password"
    type={visible.pw ? 'text' : 'password'}
    placeholder="비밀번호를 입력해주세요"
    {...register('password', {
                    required: true,
                    minLength: 8,
    })}
  />
  <div className="auth-visible-icon">
    <img id="pw" 
      src={visible.pw ? visibleEye_on : visibleEye_off}
      alt="visible-icon"
      onClick={(e) => { onClickVisible(e.target.id); }}
    />
  </div>
</div>
{errors.password?.type === 'required' && 
  (<p className="auth-err-msg">비밀번호를 입력해주세요</p>)}
{errors.password?.type === 'minLength' && 
  (<p className="auth-err-msg">비밀번호를 8자 이상 입력해주세요.</p>)}

비밀번호 필드는 필수 여부와 최소 길이를 조건으로 설정했습니다.

비밀번호 가리기 기능을 구현하기 위해 부모 컴포넌트에서 propsvisible 객체를 전달받아 사용했습니다. 자세한 건 밑에서 알아보겠습니다.
blank_pwminLength_pw

비밀번호 확인

비밀번호 확인은 이전에 입력했던 비밀번호와 비교하여 같다면 유효성 검사를 통과하게끔 만들었습니다.

<div id="pw-check-box" className="pw-box">
  <label htmlFor="pw-check" className="basic-p pw-label">
    비밀번호 확인
  </label>
  <input
    className={`input-box pw ${errors.confirmPassword ?'auth-err-border' : ''}`}
    name="checkPassword"
    type={visible.checkPw ? 'text' : 'password'}
    placeholder="비밀번호를 다시 한 번 입력해주세요"
    {...register('confirmPassword', {
                    required: true,
                    minLength: 8,
                    validate: () =>
                      getValues('password') === getValues('confirmPassword'),
                  })}
  />
  <div className="auth-visible-icon">
    <img id="checkPw" 
      src={visible.checkPw ? visibleEye_on : visibleEye_off}
      alt="visible-icon"
      onClick={(e) => { onClickVisible(e.target.id); }}
    />
  </div>
</div>
{errors.confirmPassword?.type === 'minLength' && 
  (<p className="auth-err-msg">비밀번호를 8자 이상 입력해주세요.</p>)}
{errors.confirmPassword?.type === 'validate' && 
  (<p className="auth-err-msg">비밀번호가 일치하지 않습니다.</p>)}

먼저 입력했던 비밀번호의 값과 비교하기 위해 사용자 정의 조건 함수를 받는 validate 옵션에 getValues 함수를 사용해서 비밀번호의 값을 가져와서 비밀번호 확인 필드에 입력한 값과 비교했습니다.
min_lengthincorrect_pw

2️⃣ 비밀번호 보이기/가리기

이제 비밀번호 보이기/가리기 기능을 구현해보겠습니다.
해당 내용은 React-Hook-Form과는 관계 없는 내용입니다.

위에서 비밀번호 입력과 비밀번호 확인 입력 부분에 해당 코드를 작성해 놓았습니다.

{/* 비밀번호 */}
<div className="auth-visible-icon">
    <img id="pw" 
      src={visible.pw ? visibleEye_on : visibleEye_off}
      alt="visible-icon"
      onClick={(e) => { onClickVisible(e.target.id); }}
    />
  </div>

{/* 비밀번호 확인 */}
<div className="auth-visible-icon">
    <img id="checkPw" 
      src={visible.checkPw ? visibleEye_on : visibleEye_off}
      alt="visible-icon"
      onClick={(e) => { onClickVisible(e.target.id); }}
    />
  </div>

visible의 pw와 checkPw 속성 값을 통해 보이기 상태인지 가리기 상태인지 확인하여 이미지 경로를 바꾸고, 해당 input 필드의 type을 text나 password로 바꿉니다.

그리고 해당 이미지를 클릭하면 props로 전달받은 onClickVisible 함수로 이 이미지의 id와 같은 속성의 값이 변경되게끔 토글 형태로 제작했습니다.

// App.jsx
<Route element={<AuthLayout />}>
  <Route path="login" element={<Login />} />
  <Route path="signup" element={<Signup />} />
</Route>

먼저 최상단 컴포넌트 AppReact-Router를 사용하여 AuthLayout안의 자식으로 로그인, 회원가입 컴포넌트를 넣어놨습니다.

// AuthLayout.jsx
const [visible, setVisible] = useState({
    pw: false,
    checkPw: false,
  });

  const onClickVisible = (id) => {
    setVisible((prevState) => ({
      ...prevState,
      [id]: !visible[id],
    }));
  };

  return (
    <main>
      <Outlet
        context={{
          visible,
          setVisible,
          onClickVisible,
          visibleEye_off,
          visibleEye_on,
        }}
      />
    </main>
  );

AuthLayout 컴포넌트에서는 <Outlet />으로 자식 컴포넌트를 표출합니다.

여기서 <Outlet />의 props를 내려줄 때에는 context라는 속성을 통해 내려주어야 합니다.

onClickVisible 함수는 img 태그의 아이디를 매개변수로 받고, 이와 같은 visible 객체의 값을 반대로 변경시킵니다.

자식 컴포넌트들에게 전달하기 위해 비밀번호 보이기의 상태와 상태 값을 변경할 함수와 이미지 경로를 담고있는 변수들을 props로 내려줍니다.

이제 회원가입 페이지 컴포넌트에서

...
<input
    className={`input-box pw ${errors.password ? 'auth-err-border' : ''}`}
    name="password"
    type={visible.pw ? 'text' : 'password'}
    placeholder="비밀번호를 입력해주세요"
    {...register('password', {
                    required: true,
                    minLength: 8,
    })}
  />
...

비밀번호와 비밀번호 확인 input의 type을 상태에 맞게 변경해주고 확인해보겠습니다.
show_pw👍👍

하지만 비밀번호 보이기/가리기 state를 로그인 페이지와 같이 사용하기 때문에 로그인 페이지에서 비밀번호를 보이게 설정하면, 회원가입 페이지에서도 비밀번호가 보이는 상태가 된 채로 이동하게 됩니다.

기본적으로 비밀번호를 가리는 상태로 만들기 위해 로그인과 회원가입 컴포넌트에 useEffect훅을 사용하여 기본 state의 값을 false로 만들어 두겠습니다.

useEffect(() => {
    setVisible((prevState) => ({
      ...prevState,
      pw: false,
    }));
  }, []);

이렇게 React-Hook-Form을 이용하여 유효성 검사를 진행하고 비밀번호 표시 기능까지 만들어 보았습니다. 긴 글 읽어주셔서 감사합니다.

참조
짱잼이의 FE 개발 공부 저장소
React Hook Form
https://leejams.github.io/

profile
차근차근 기록하기

0개의 댓글