팀프로젝트를 진행하면서 폼을 쓰는 곳이 많아졌다. 그러다가 한 페이지에서 코드가 300줄이 넘어가면서 컴포넌트 분리의 필요성을 느꼈고, 폼 내에서 컴포넌트 분리 방법을 찾다가 React Hook Form 이라는 라이브러리를 발견했다.
처음에 적용시키는 과정이 너무 어려웠어서, 적용시키는 과정을 정리하며 공유할까 한다.
회원가입과 로그인 등 폼을 작성할 일이 생길 수 있다. 이 때 그냥 폼을 작성하면 유효성 검사를 위한 if 문 등 엄청나게 많은 코드가 작성된다. React Hook Form 은 비제어 컴포넌트 방식으로 구현되어, 렌더링 이슈를 해결해준다. 또한 React Hook Form에서 직관적인 유틸을 제공하기에 전체적인 코드 양도 줄고 단순해져서 가독성이 좋아진다.
바닐라 JS와 비슷하다고 생각하면 된다. 이 때 값은 ref를 통해서 얻는다. 이 말은 제어 컴포넌트와 다르게 값이 실시간으로 동기화 되지 않는다.(= 값이 변경되어도 리랜더링 되지 않는다.)
console.log()에 값을 찍으면 submit 버튼 누르기 전까지 안 찍힘
사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트 한다. 간단히 말하면 react에 의해 값이 제어가 되므로 setState()
로 값을 저장하는 방식이라고 생각하면 되겠다.
console.log()에 값을 찍으면 값을 누를 때 마다 바로 바로 찍힌다.
npm install react-hook-form
import { useForm } from "react-hook-form";
const My = function () {
const { register, handleSubmit, watch, formState: { errors } } = useForm();
const onSubmit = (data) => console.log(data);
console.log(register())
console.log(errors)
console.log(watch("example"));
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>이름</label>
<input defaultValue="test" {...register("example")}/>
<input {
...register("exampleRequired",
{required : true,
minLength: 2,
maxLength: 5
}
)}/>
{errors.exampleRequired?.type === "minLength" &&
"이름은 최소 2글자 이상이어야 합니다."}
{errors.exampleRequired?.type === "maxLength" && "이름은 최대 5글자입니다."}
{errors.exampleRequired?.type === "required" && <span>필수값입니다.</span>}
<button type="submit" />
</form>
);
};
export default My;
const { register, handleSubmit, watch, formState: { errors } } = useForm();
이 useForm은 객체로 구성되어 있다.
여러가지 메소드가 들어가 있는데, 여기서는 일부만 썼다.
일반적으로
register
와handleSubmit
이 많이 쓰인다.
그 다음 줄에 쓰인
const onSubmit = (data) => console.log(data);
form에 입력한 데이터들을 받아 console에 출력해주는 용도다.
console.log(register())
register 함수를 실행하면 event controller에 onChange, onBlur, ref 등의 함수가 자동으로 생성이 된다.
이 함수들을 통해 React Hook Form이 form의 변화를 감지하고 validation(유효성 검사)나 submission(제출)이 가능하게 된다.
따라서 입력을 받고자 하는 모든 필드에는 반드시 register 함수가 사용된다.
validation(유효성 검사)이 정상적으로 완료된 데이터는 handleSubmit
함수를 통해 받을 수 있다.
const onSubmit = (data) => console.log(data);
<form onSubmit={handleSubmit(onSubmit)}>
위처럼 작성하게 되면 form을 제출할 때, onSubmit
함수를 통해 React Hook Form에서 받은 object가 콘솔에 출력된다.
<input {
...register("exampleRequired",
{required : true,
minLength: 2,
maxLength: 5
}
)}/>
required (필수 여부), min (최소 값), max(최대값), minLength(최소 길이), maxLength(최대 길이), pattern(정규 표현식), validate (custom validation 함수)
validation은 register의 2번째 인자로 들어간다.
위와 같이 간단하게 알아볼 수 있기 때문에 아직은 yup 라이브러리 까지 쓸 필요성은 못 느꼈다.
validation 조건에 부합하지 않으면 error 메세지가 출력되게 해보자.
<input {
...register("exampleRequired",
{required : true,
minLength: 2,
maxLength: 5
}
)}/>
{errors.exampleRequired?.type === "minLength" &&
"이름은 최소 2글자 이상이어야 합니다."}
{errors.exampleRequired?.type === "maxLength" && "이름은 최대 5글자입니다."}
{errors.exampleRequired?.type === "required" && <span>필수값입니다.</span>}
위의 useForm 객체 부분을 다시 보게 되면
const { register, handleSubmit, watch, formState: { errors } } = useForm();
다음과 같은 메서드들이 존재하는데, 여기서 4번째 formState 객체 안에 errors 객체가 포함되어 있다.
useForm 공식문서
다른 객체를 참고하고 싶다면 위의 공식문서를 봐보도록 하자.
console.log(errors);
를 하게 되면
다음과 같이 input의 이름에 따라 에러가 저장되고, 에러가 난 type 을 보여준다.
그리고 하단에
{errors.exampleRequired?.type === "minLength" &&
"이름은 최소 2글자 이상이어야 합니다."}
을 통해 에러 메세지를 출력할 수 있다.
이를 통해 기초를 알아보았고, 이제는 useForm의 공식문서를 살펴보면서 심화해 나가보도록 하자.
나는 styled-components로 input과 button을 쓰고 있다.
그러다보니 ref 부분에서 문제가 발생했다.
이 문제를 위해 정말 많은 search 를 했고, 정확한 원인을 파악하고 해결 방법을 알려준 블로그를 발견했다.
출처 : 블로그
간단히 말하면 리액트에서 부모 - 자식 간 데이터를 주고 받으려면 props를 이용한다.
그러나 ref는 인스턴스에 접근하여 직접 자식 데이터를 읽고 수정하는게 가능하다.
나는 함수형 컴포넌트를 쓰고 있는데, 함수형 컴포넌트에서는 인스턴스가 없다.
따라서 이 상태에서 ref를 쓴다는 것은 빈 값을 쓰는 것과 동일하다는 것이다.
이를 해결하기 위해 forwardRef를 쓴다.
해결 방법만 빠르게 쓴다면
기존 자식 input
자식 input 컴포넌트 내에서
import { forwardRef } from "react";
를 선언해주고
와 같이 forwardRef와 return 내에 ref를 선언해주면 된다.