리액트로 회원가입 페이지 제작을 하면서 유효성 검사를 어떻게 할 지 고민하다
React-Hook-Form
이라는 라이브러리를 발견해서 이를 사용해보기로 했습니다.
백엔드 처리는 하지 않고 화면만 개발했습니다.
라이브러리 사용을 위해 npm을 통해 설치해주겠습니다.
npm install react-hook-form
라이브러리를 설치했다면 React-Hook-Form
을 사용해보겠습니다.
useForm()
훅을 이용해서 Form 기능을 관리하는 값들을 가져옵니다.
const {
register,
formState: { errors },
handleSubmit,
watch,
getValues,
} = useForm({ mode: 'onBlur' });
해당 훅을 통해 반환 된 값들을 구조 분해 할당을 통해 담아주었습니다.
register
formState: { errors }
true
로, 성공이라면, false
가 됩니다. 다른 속성들이 더 있지만 errors만 사용했습니다.handleSubmit
watch
watch('registerName')
로 해당하는 필드의 값의 변경을 감시하거나 watch(['registerName1', 'registerName2'])
로 여러 개의 값을 감시할 수 있습니다.getValues
getValues('registerName')
useForm({mode: ''})
'onBlur'
가 되면 유효성 검사를 수행하도록 했지만 'onChange'
로 바꾼다면 입력될 때마다 유효성 검사가 가능합니다.저는 간단한 회원가입 페이지라 이메일, 닉네임, 비밀번호, 비밀번호 확인
만 만들었습니다.
우선 이메일을 먼저 만들어보겠습니다.
...
<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
을 통해 해당하는 필드의 유효성 검사 여부를 확인했습니다. 이제 이메일 입력 필드에서 다른 곳을 클릭하면 유효성 검사 결과 스타일이 적용됩니다.
...
<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.nickname
이 undefined
상태일 때 type
속성에 접근 하려고 하여 에러가 발생합니다.
비밀번호 입력 칸에는 비밀번호 보이기 / 가리기 기능을 넣기 위해 눈 모양 아이콘을 추가 했습니다.
<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>)}
비밀번호 필드는 필수 여부와 최소 길이를 조건으로 설정했습니다.
비밀번호 가리기 기능을 구현하기 위해 부모 컴포넌트에서 props
로 visible
객체를 전달받아 사용했습니다. 자세한 건 밑에서 알아보겠습니다.
비밀번호 확인은 이전에 입력했던 비밀번호와 비교하여 같다면 유효성 검사를 통과하게끔 만들었습니다.
<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
함수를 사용해서 비밀번호의 값을 가져와서 비밀번호 확인 필드에 입력한 값과 비교했습니다.
이제 비밀번호 보이기/가리기 기능을 구현해보겠습니다.
해당 내용은 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>
먼저 최상단 컴포넌트 App
에 React-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을 상태에 맞게 변경해주고 확인해보겠습니다.
👍👍
하지만 비밀번호 보이기/가리기 state를 로그인 페이지와 같이 사용하기 때문에 로그인 페이지에서 비밀번호를 보이게 설정하면, 회원가입 페이지에서도 비밀번호가 보이는 상태가 된 채로 이동하게 됩니다.
기본적으로 비밀번호를 가리는 상태로 만들기 위해 로그인과 회원가입 컴포넌트에 useEffect
훅을 사용하여 기본 state의 값을 false로 만들어 두겠습니다.
useEffect(() => {
setVisible((prevState) => ({
...prevState,
pw: false,
}));
}, []);
이렇게 React-Hook-Form을 이용하여 유효성 검사를 진행하고 비밀번호 표시 기능까지 만들어 보았습니다. 긴 글 읽어주셔서 감사합니다.
참조
짱잼이의 FE 개발 공부 저장소
React Hook Form
https://leejams.github.io/