웹 어플리케이션을 만들다 보면 수 많은 Form 들을 만나게 된다. 상황에 따라서는 단순히 2~3개의 입력이 아니라 여러 개의 입력을 받고 유효성 검증까지 함께 해야하기 때문에 매번 골머리를 않는 부분이기도 하다. 특히, 사용자의 입력마다 유효성을 검증하고 에러 메세지를 보여줘야 하는 경우에는 렌더링 최적화에 대한 고민이 들어가게 되고, 이를 이해 코드를 작성하다 보면 또 엄청 복잡한 코드를 마주하고는 인상을 쓰게 된다. 팀 간에 협업을 진행하면서도 Form을 어떻게 작성할 지 협의가 쉽지 않아지는 포인트이기도 하다. 이를 개선하기 위해서 도움을 받을 수 있는 라이브러리가 있는 지 찾아보게 되었고 나는 React-hook-form 은 어떤 라이브러리인지, 어떤 문제를 도와줄 수 있는지 살펴보려고 한다.
# 지원하는 리액트 버전
npm i react-hook-form
# 지원하지 않는 리액트 버전
npm i react-hook-form --legacy-peer-deps
const App = () => {
const [values, setValues] = useState({
email: "",
password:"",
passwordCheck:"",
phone:"",
agreement: "",
})
const onChange = (e:React.ChangeEvent<HTMLInputElement>) => {
const {name, value} = e.target;
setValues(prev => ({
...prev, [name] : value.trim()
}))
}
return(
<Container>
<Input name="email" value={values.email} onChange={onChange}/>
<Input name="password" value={values.password} onChange={onChange}/>
<Input name="passwordCheck" value={values.passwordCheck} onChange={onChange}/>
<Input name="phone" value={values.phone} onChange={onChange}/>
<Input name="agreement" value={values.agreement} onChange={onChange} type="checkBox"/>
</Container>
)
}
기존에는 폼을 작성하기 위해서 신경 쓸 부분이 많았다. 각 input에 대한 상태값을 관리할 필요가 있었고 이를 위해 각 상태를 따로 작성하거나 하나의 useState에 객체로 관리하거나 해야했다. 여기에 각 입력마다 error 상태를 관리하기 위해서는 error에 관한 상태를 또 만들고 매 입력마다 이를 확인하는 로직을 추가해야 했다. React-hook-from을 사용하면 이를 훨씬 간단하게 작성할 수 있다.
useForm을 이용하면 번거롭고 복잡한 코드를 간단하게 작성할 수 있다.
import React from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName")} />
<select {...register("gender")}>
<option value="female">female</option>
<option value="male">male</option>
<option value="other">other</option>
</select>
<input type="submit" />
</form>
);
}
우리가 연결하고자 하는 Input 에 register 을 이용해서 연결할 수 있는데, 이때 인자로 넣어주는 문자열은 name이라고 생각하면 된다.
handleSubmit의 경우에는 submit의 기본 이벤트가 발생할 때 리프레쉬 되는 현상을 막아주면서 콜백으로 전달된 함수에 input에 담긴 value 들을 담아 실행시켜주기 때문에 복잡하게 추가 로직을 작성할 필요 없이 사용할 수 있다.
위의 register 방식을 통하면 손쉽게 form을 관리할 수 있지만 다른 라이브러리에서 제공하는 컴포넌트나 내부에서 작성한 컴포넌트의 경우에는 바로 register을 적용할 수 없다. 이를 대비해서 Controller 가 제공된다.
const {
control,
getValues,
formState: {errors, isValid}
} = useForm({mode: "onChange", reValidateMode: "onChange"})
return <InputWrapper>
<Controller
render={({ field }) => (
<WithLabelInput
type="text"
label="이름"
{...field}
/>
)}
control={control}
name="이름"
rules={{ required: true }}
defaultValue=""
/>
<SpaceY space="8px" />
{errors?.이름 && (
<WarnMessage
message="이름을 입력해주세요."
isError
fontSize="14px"
/>
)}
</InputWrapper>
Controller에 각• 요소는 다음과 같다.
간단하게 작성해 본 입력 modal의 모습이다. 각 input의 유효성을 실시간으로 검색하면서도 리렌더링을 유발시키지 않고 있다. 또한, 동일한 동작을 하는 기능임에도 불구하고 훨씬 적은 코드를 작성할 수 있었다.
이렇게 React-hook-form에 대해서 간단하게 살펴 봤다. 당장 프로덕트에 적용한 것이 아니라 검토하는 과정에서 간단하게 작성을 해봤기 때문에 실제로 사용하면서 많은 문제들이 발생할 여지는 있다. 항상 일하면서 느끼는 부분이지만 개발이 들어가게 되는 경우에 생각보다 많은 변수들이 발생하고 이를 해결하기 위해서 라이브러리와 고생을 해본 경험은 많으니까. 그렇기 때문에 직접 작성하고 최적화만 잘하면 되지 않겠냐는 생각이 들 수도 있고, 한 적도 있다. 그럼에도, 라이브러리를 사용해서 얻을 수 있는 이점은 불필요한 코드의 양을 줄이고 협업하는 과정에서 의사 소통에 들어가는 시간의 낭비를 줄일 수 있는 부분이라고 생각한다. 적은 코드를 작성하면 버그도 줄어들테니까. 다음 React-hook-form과 관련된 글은 실제로 적용하면서 겪었던 문제들과 이를 해결하는 과정에 대해서 정리해보려고 한다.