이전의 제로초 강의를 들으면서 만들었던 form 은 일일히 state 를 만들어 사용하거나 커스텀훅을 이용해 만들었는데, 이번엔 리액트의 react-hook-form 이라는 라이브러리를 이용해 폼을 다시 구현해보았다.
state 를 만들 필요가 없고, 유효성 검사나 실시간 value 값 감시 등 직접 만들어야했던 기능들이 전부 자동화 되어있어 매우 편리했다.
기존 코드 (react-hook-form 사용 전):
// singup.js
import React, { useCallback, useState } from "react";
import AppLayout from "../components/AppLayout";
import Head from "next/head";
import { Button, Checkbox, Form, Input } from "antd";
**import useInput from "../hooks/useInput";**
import styled from "styled-components";
const ErrorMessage = styled.div`
color: red;
`;
const Signup = () => {
// 커스텀 훅으로 중복 제거
// => 다음과 같이 훅들이 중복되면 커스텀 훅으로 간단하게 사용할 수 있다.
const [id, onChangeId] = useInput("");
const [nickname, onChangeNickname] = useInput("");
const [password, onChangePassword] = useInput("");
// passwordCheck 는 useCallback 에서 다른 부분이 있기 때문에 커스텀 훅 x
const [passwordCheck, setPasswordCheck] = useState("");
// '비밀번호'가 '비밀번호 확인'과 일치하는지 체크
const [passwordError, setPasswordError] = useState(false);
const onChangePasswordCheck = useCallback(
(e) => {
setPasswordCheck(e.target.value); // 이것 하나만 있었다면 커스텀 훅으로 묶음
setPasswordError(e.target.value !== password); // 비밀번호 비교
},
[password]
);
// setTerm 역시 다른 부분이 있기 때문에 커스텀 훅 x
// 약관에 체크안한 상태로 제출버튼 누르면 termError 실행
const [term, setTerm] = useState("");
const [termError, setTermError] = useState(false);
const onChangeTerm = useCallback((e) => {
setTerm(e.target.checked);
setTermError(false);
}, []);
// 제출 버튼 누를시 에러 한 번더 체크
// 사용자로부터 받는 input 은 여러 번 체크할 필요성이 있다
// onFinish: e.preventDefault() 필요 x
const onSubmit = useCallback(() => {
if (password !== passwordCheck) {
return setPasswordError(true);
}
if (!term) {
return setTermError(true);
}
// 서버로 데이터가 잘 가는지 확인
console.log(id, nickname, password)
}, [password, passwordCheck, term]);
return (
<AppLayout>
<Head>
<title>회원 가입 | NodeBird</title>
</Head>
<Form onFinish={onSubmit}>
<div>
<label htmlFor="user-id">아이디</label>
<br />
<Input name="uset-id" value={id} required onChange={onChangeId} />
</div>
<div>
<label htmlFor="user-nick">닉네임</label>
<br />
<Input
name="user-nick"
value={nickname}
required
onChange={onChangeNickname}
/>
</div>
<div>
<label htmlFor="user-password">비밀번호</label>
<br />
<Input
name="user-password"
type="password"
value={password}
required
onChange={onChangePassword}
/>
</div>
<div>
<label htmlFor="user-password-check">비밀번호 체크</label>
<br />
<Input
name="user-password-check"
type="password"
required
onChange={onChangePasswordCheck}
/>
{/* passwordError 부분이 true 가 되면 에러 표시 */}
**{passwordError && (
<ErrorMessage style={{ color: "red" }}>
* 비밀번호가 일치하지 않습니다.
</ErrorMessage>
)}**
</div>
{/* 약관동의 체크박스 */}
<div>
**<Checkbox name="user-term" checked={term} onChange={onChangeTerm}>
제로초 말을 잘 들을 것을 동의합니다.
</Checkbox>
{termError && <ErrorMessage>약관에 동의하셔야 합니다.</ErrorMessage>}**
</div>
{/* 제출 버튼 (primary => 파란색 버튼) */}
**<div style={{ marginTop: 10 }}>
<Button type="primary" htmlType="submit">가입하기</Button>
{/* htmlFor="submit" 인 상태의 버튼을 누르면 Form 의 onFinish 발동 */}
</div>**
</Form>
</AppLayout>
);
};
export default Signup;
react-hook-form 사용 후:
// signup.js
import AppLayout from '../components/AppLayout';
import Head from "next/head";
import { useForm } from "react-hook-form";
import {useMemo} from "react";
// let renderCount = 0
const signup = () => {
const { register, watch, getValues, handleSubmit, reset, formState: { errors } } = useForm({
defaultValues: {
id: '',
password: '',
passwordCheck: '',
}
})
// renderCount++
console.log('에러: ', errors)
const onSubmit = (data) => {
console.log('submit')
console.log(data)
reset();
}
const marginBottom = useMemo(() => ({ display: 'block', marginBottom: '10px', fontWeight: '600', fontSize: '18px' }), [])
return (
<>
<AppLayout>
<Head>
<title>회원가입 | Buzzy</title>
</Head>
{/*<div>{renderCount}</div>*/}
<form onSubmit={handleSubmit(onSubmit)}>
<div className="field" style={{marginBottom: '30px'}}>
<label htmlFor="id" style={marginBottom}>아이디</label>
<input
{...register('id', {
required: '아이디를 입력해주세요',
minLength: {
value: 5,
message: '5자 이상으로 입력해주세요'
},
validate: {
noAdmin: (value) => value !== "admin" || "admin 이라는 아이디는 사용할 수 없습니다.",
},
})}
id="id"
maxLength="15"
type="text"
placeholder='아이디(5자 이상)' />
<p>
{errors.id && <span>* {errors.id.message}</span>}
</p>
</div>
<div className="field">
<label htmlFor="password" style={marginBottom}>비밀번호</label>
<input
{...register('password', {
required: '비밀번호를 입력해주세요',
pattern: {
value: /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,20}$/,
message: '영문, 숫자, 특수문자 포함 8 ~ 20자로 입력해주세요'
}
})}
type="password"
maxLength="15"
placeholder='비밀번호(영문, 숫자, 특수문자 포함 8자 ~ 20자)'/>
<p>
{errors.password && <span>* {errors.password.message}</span>}
</p>
</div>
<div className="field" style={{marginBottom: '30px'}}>
<input
{...register('passwordCheck', {
required: "비밀번호를 확인해주세요",
validate: {
matchPassword: (value) => {
const { password } = getValues();
return password === value || '비밀번호가 일치하지 않습니다'
}
}
})}
type="password"
maxLength="15"
placeholder='비밀번호 확인' />
<p>
{errors.passwordCheck && <span>* {errors.passwordCheck.message}</span>}
</p>
</div>
<div className="field" style={{marginBottom: '30px'}}>
<input
{...register('term', {
required: "약관에 동의해주세요",
})}
type="checkbox"
id="term"/>
<label htmlFor="term">조혜진에게 복종할 것에 동의합니다</label>
<p>
{errors.term && <span>* {errors.term.message}</span>}
</p>
</div>
<button type="submit">회원가입</button>
</form>
</AppLayout>
</>
);
};
export default signup;
유효성 검사:
해결 출처: https://velog.io/@bomboming/React-Hooks-Form을-사용한-유효성검사