React 17에서 Custom Hook으로 폼 상태 관리 개선하기
문제 상황
회원가입, 프로필 수정, 게시글 작성 등 여러 페이지에서 폼을 다루는 코드가 반복되고 있었다. 각 컴포넌트마다 useState로 필드를 관리하고, onChange 핸들러를 작성하고, validation 로직을 구현하는 패턴이 계속 중복됐다.
useForm 훅 구현
공통 로직을 추출해 Custom Hook으로 만들었다.
import { useState, useCallback } from 'react';
function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = useCallback((e) => {
const { name, value } = e.target;
setValues(prev => ({ ...prev, [name]: value }));
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: null }));
}
}, [errors]);
const handleSubmit = useCallback(async (onSubmit) => {
const validationErrors = validate(values);
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
setIsSubmitting(true);
try {
await onSubmit(values);
} catch (error) {
setErrors({ submit: error.message });
} finally {
setIsSubmitting(false);
}
}, [values, validate]);
return { values, errors, isSubmitting, handleChange, handleSubmit };
}
export default useForm;
사용 예시
로그인 폼에 적용한 코드다.
function LoginForm() {
const validate = (values) => {
const errors = {};
if (!values.email) errors.email = '이메일을 입력하세요';
if (!values.password) errors.password = '비밀번호를 입력하세요';
return errors;
};
const { values, errors, isSubmitting, handleChange, handleSubmit } = useForm(
{ email: '', password: '' },
validate
);
const onSubmit = async (data) => {
await api.login(data);
};
return (
<form onSubmit={(e) => {
e.preventDefault();
handleSubmit(onSubmit);
}}>
<input name="email" value={values.email} onChange={handleChange} />
{errors.email && <span>{errors.email}</span>}
<input name="password" type="password" value={values.password} onChange={handleChange} />
{errors.password && <span>{errors.password}</span>}
<button disabled={isSubmitting}>로그인</button>
</form>
);
}
결과
반복 코드가 크게 줄었고, 폼 로직의 일관성도 높아졌다. react-hook-form 같은 라이브러리 도입도 고려했지만, 프로젝트 규모에서는 이 정도 추상화로 충분했다.