리액트로 캡스톤 프로젝트를 진행하며 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에 대한 설명input
border-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