
리액트로 캡스톤 프로젝트를 진행하며 react-hook-form을 도입하게 되었다!
기존에 노마드코더의 리액트 강의(다 듣지도 않았음..)에서 react-hook-form을 알게 되고, 이런 편리한 기능을 가진 라이브러리가 있다니!! 하며 놀란 기억이 있다. 그동안 프로젝트에서 사용하지 못하다가, 이번 캡스톤 프로젝트에서 혼자 프론트엔드 개발을 진행하게 되어 react-hook-form을 도입했다.
그래서 왜 또 굳이굳이 라이브러리를 도입하는데? 그냥
useState로 상태관리 열심히 하면 되잖아
맞다. 그냥 상태관리 빡세게(?) 하면 되지.... 역시 답은 하드코딩이지!! 그럼에도 해당 기술을 도입하게 된 이유는 이런 과정이 매우 귀찮기 때문이다.
리액트 form 상태 관리에 어려움을 느낀 건 나의 첫 번째(!) 프론트엔드 프로젝트인 에코노삡 프로젝트였다. 책의 정보를 form 형태로 받아야 했는데, 정보가 무려 9가지나 된다.
사실 1차원적인 해결방법은 어렵지 않다. useState hook을 활용해 9개의 상태를 선언해주면 된다. 다만 form을 만들 때 우리는 상태만 필요한 것이 아니라는 점....

input이 단 두 가지 뿐인데 해야 할 일이 많고 반복적이다.
상태가 9가지인 에코노삡의 코드는 개발을 진행한 내가 봐도 이해하기 어려웠다.
난 분명 공통되는 로직과 컴포넌트를 재사용하고 싶어서 리액트를 사용했는데 이슈가 해결되지 않았다. 컴포넌트의 가독성이 매우 낮고 복잡해 유지보수가 어려웠다. 리액트의 장점을 다 죽이고 있었다.
나의 첫 번째 프로젝트는 이렇게 끝났다. Javascript도 겉만 핥고 4개월 동안 리액트 학습과 동시에 진행한 프로젝트라 아쉬움이 컸다. 또한 프로젝트를 완성하고 싶은 욕심에 아예 처음부터 리팩토링을 시작했다.

inputInfo)에서 관리함(e) => {setInputInfo({... inputInfo, new: e.target.value}로 통일alert 기능을 활용해 성공/실패 여부를 알림이번 캡스톤 프로젝트은 form이 많이 사용된다. 그리고 디자인 단계부터 컴포넌트 재사용성을 고려해 반복되는 디자인이 특징이다. 수많은 form을 보고 이번 프로젝트에 react-hook-form을 도입하기로 결정했다.
react-hook-form은...
form이나 애플리케이션의 루트에서 변경되는 다른 form 값으로 인해 발생하는 리렌더링의 양을 줄여준다<form> 전체를 다시 렌더링하지 않고도 개별 <input> 입력과 form 상태 업데이트를 구독할 수 있다아래의 코드는 email과 username을 입력받아 콘솔에 출력한다.
import { useForm } from "react-hook-form";
const Example = () => {
const { handleSubmit, register, formState: { errors } } = useForm();
const onSubmit = values => console.log(values);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="email"
{...register("email", {
required: "Required",
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "invalid email address"
}
})}
/>
{errors.email && errors.email.message}
<input
{...register("username", {
validate: value => value !== "admin" || "Nice try!"
})}
/>
{errors.username && errors.username.message}
<button type="submit">Submit</button>
</form>
);
};
먼저 useForm 훅을 사용한다. configuration 옵션으로 mode와 defaultValues를 넘길 수 있다.
mode로 validation 전략을 설정할 수 있다. onSubmit, onChange, onBlur, all 등의 옵션이 있는데, mode가 onChange일 경우 다수의 리렌더링으로 성능에 영향을 끼칠 수 있어 주의해야 한다.
defaultValues로 form의 기본 값을 제공할 수 있다.
register을 사용해 input 또는 select 요소를 등록하고 유효성 검사 규칙을 적용할 수 있다.
<input
type="email"
{...register("email", {
pattern: {
value:
/^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i,
message: "이메일 형식에 맞지 않습니다.",
},
})}
/>;
register의 첫 번째 매개변수는 name이다. 해당 필드를 다루는 key 값으로 필수이며 고유해야 한다. name은 숫자로 시작하거나 숫자를 key 이름으로 사용할 수 없다.
register의 두 번재 매개변수는 option 객체다. 해당 객체에는 유효성 검사를 위한 프로퍼티들이 들어갈 수 있다. 유효성 검사를 위한 value와 더불어 message로 구성된 객체를 사용해, 에러에 대한 구체적인 메세지를 제공할 수 있다.
에러에 대한 정보는 formState 객체의 errors에 들어있다. 에러가 존재하지 않는다면 해당 객체는 비어있다.

이제 react-hook-form의 기본적인 동작 방법은 알게 되었다. 이제 프로젝트에 적용해보자!


label: input에 대한 설명inputborder-color가 red로 변함 + input 아래에 red 문장 나타남border-color가 red로 변함 + input 아래에 red 문장 나타남blue 문장 나타남useForm 훅을 매번 필요할 때마다 사용하는 것이 귀찮을 것 같아서 재사용이 가능한 Form.jsx 컴포넌트를 만들었다. 코드는 아래와 같다.

먼저 useForm 훅을 사용해 methods 객체를 생성했다. 필요한 프로퍼티만 선언하지 않고, 모든 함수와 객체를 리턴 받은 이유는 <FormProvider>를 사용하기 위해서다.
useFormContext 훅을 사용하면 form context에 액세스할 수 있다. 이 훅은 context를 프로퍼티로 전달하는 것이 불편한 중첩된 구조에서 사용하기 위한 것이다.
useFormContext를 사용하기 위해서는 <FormProvider> 컴포넌트로 form을 감싸 주어야 한다.
import React from "react";
import { useForm, FormProvider, useFormContext } from "react-hook-form";
export default function App() {
const methods = useForm();
const onSubmit = data => console.log(data);
return (
<FormProvider {...methods} > // pass all methods into the context
<form onSubmit={methods.handleSubmit(onSubmit)}>
<NestedInput />
<input type="submit" />
</form>
</FormProvider>
);
}
function NestedInput() {
const { register } = useFormContext(); // retrieve all hook methods
return <input {...register("test")} />;
}
useFormContext 훅을 사용해 register와 getValues를 props로 전달받지 않고 form context에 액세스할 수 있었다.

Form.jsx를 사용해 로그인 페이지를 아래의 코드로 간단히 구현할 수 있다.

더불어 나는 기존에 prop type checking을 귀찮다는 이유로 하지 않았는데, 컴포넌트의 재사용을 쉽게 하기 위해서는 필요성을 깨닫고 PropTypes를 사용했다.
Form.jsx의 propTypes는 아래와 같다. arrayOf와 exact 기능을 사용해 객체로 이루어진 배열에 대한 type도 지정할 수 있었다.
Form.propTypes = {
onSubmit: PropTypes.func.isRequired,
onError: PropTypes.func,
defaultValues: PropTypes.object.isRequired,
inputInformations: PropTypes.arrayOf(
PropTypes.exact({
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
required: PropTypes.object,
requireMessage: PropTypes.string,
})
).isRequired,
};
react-hook-form을 이용해 form의 상태를 보다 쉽게 관리할 수 있었다. 또한 코드의 가독성이 기존의 방식보다 뛰어나다. 특히 에러 처리가 매우 쉬웠다. 웹 어플리케이션의 퍼포먼스뿐만 아니라 코드 작성이 편리하다. <FormProvider>와 useFormContext를 활용해 재사용할 수 있게 컴포넌트화도 가능하다. form을 많이 사용하는 경우 강추하고 싶다!
자세한 코드는 github에서 확인할 수 있다.
react-hook-form 공식 홈페이지
react-hook-form 을 활용해 효과적으로 폼 관리하기
Typechecking With PropTypes