Performant, flexible and extensible forms with easy-to-use validation.
react-hook-form은 React 애플리케이션에서 폼을 쉽게 관리하기 위한 라이브러리 중 하나입니다. 이 라이브러리는 더 나은 성능과 사용자 경험을 제공하며, React 컴포넌트의 상태 및 라이프사이클을 활용하여 폼 상태를 관리합니다.
사용법을 먼저 간단하게 소개합니다.
import { useForm } from 'react-hook-form';
function MyForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = data => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("좋아요")} />
<input {...register("싫어요")} />
<button type="submit">Submit</button>
</form>
);
}
위처럼 input
에 register
를 다음과 같이 넘기면 {...register("좋아요")}
해당 key
로 등록이 되고 이후 폼 제출시 확인이 가능합니다.
그런데 HTML input
태그를 바로 사용하는 것이 아니라 개발자가 Input
컴포넌트를 따로 만들어서 register
를 하려고 하면 어떻게 될까요? label을 함께 보여주는 MyInput
컴포넌트를 만들고 똑같이 register
함수를 사용해봅니다.
import { useForm } from 'react-hook-form';
interface MyInputProps extends React.HTMLProps<HTMLInputElement> {
label: string;
}
const MyInput = ({ label, ...props }: MyInputProps) => {
return (
<label className='flex'>
<span className='mr-2 text-2xl'> {label}</span>
<input {...props} />
</label>
);
};
export default function Home() {
const { register, handleSubmit } = useForm();
function onSubmit(data: any) {
console.log(data);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<MyInput label='😀' {...register('좋아요')} />
<button type="submit">Submit</button>
</form>
);
}
랜더링시에 에러가 발생하며 동시에 폼 제출시 값도 잘 전달되지 않는 것을 확인할 수 있습니다. 두 가지 해결방법을 알려드리겠습니다.
커스텀 컴포넌트에서 직접 register
함수를 사용하려고 했더니 잘 동작하지 않았습니다. 그렇다면 props
로 넘겨서 사용하면 그만입니다.
interface MyInputProps extends React.HTMLProps<HTMLInputElement> {
label: string;
name: string;
register?: any;
}
const MyInput = ({ label, name, register, ...props }: MyInputProps) => {
return (
<label className='flex'>
<span className='mr-2 text-2xl'> {label}</span>
<input {...register(name)} {...props} />
</label>
);
};
export default function Home() {
const { register, handleSubmit } = useForm();
const onSubmit = (data: any) => console.log(data);
return (
<main className='flex min-h-screen flex-col items-center justify-between p-24'>
<form onSubmit={handleSubmit(onSubmit)}>
<MyInput label='😀' register={register} name='좋아요' />
<button type='submit'>Submit</button>
</form>
</main>
);
}
랜더링시 에러도 뜨지 않고 폼 제출시 값도 잘 가져옵니다. 하지만 이 방법에는 여러 문제가 있습니다.
먼저 register
함수에 태깅할 name
이 props
에 반드시 추가되어야 합니다. 그리고 register
함수는 사용시 유효성 검사를 포함한 여러가지 옵션을 추가로 인자로 넘겨줄 수 있습니다. 그렇다면 이를 위해 props
를 또 추가해야 할 것입니다.
<input {...register("firstName", { required: true, maxLength: 20 })} />
<input {...register("lastName", { pattern: /^[A-Za-z]+$/i })} />
<input type="number" {...register("age", { min: 18, max: 99 })} />
그리고 register
를 props
로 넘겨줄 때 interface
의 타입 문제입니다. 물론 any
를 사용하지 않고 라이브러리에서 제공하는 타입을 지정해줄수 있습니다. 하지만 any
를 지우기 위해서 꽤 공을 들여야 하고 결과적으로 기존의 방식대로 register
를 사용하지 못하는 큰 단점을 해결할 수 없습니다.
import { useForm, UseFormRegister, FieldValues } from 'react-hook-form';
interface MyInputProps extends React.HTMLProps<HTMLInputElement> {
label: string;
name: string;
register: UseFormRegister<FieldValues>;
}
const MyInput = ({ label, name, register, ...props }: MyInputProps) => {
return (
<label className='flex'>
<span className='mr-2 text-2xl'> {label}</span>
<input {...register(name)} {...props} />
</label>
);
};
export default function Home() {
const { register, handleSubmit } = useForm();
const onSubmit = (data: any) => console.log(data);
return (
<main className='flex min-h-screen flex-col items-center justify-between p-24'>
<form onSubmit={handleSubmit(onSubmit)}>
<MyInput label='😀' register={register} name='좋아요' />
<button type='submit'>Submit</button>
</form>
</main>
);
}
props
방식으로는 이런식 저런식으로 어떻게든 해결은 할 수 있으나 본 라이브러리를 적절히 사용하는 것으로 보이지는 않습니다. 그렇다면 어떻게 해결할까요?
실은 에러메세지에서 힌트를 찾을 수 있습니다. React.forwardRef()
를 사용하려는 건지 물어보고 있습니다.
Exposing a DOM node to the parent component
By default, each component’s DOM nodes are private. However, sometimes it’s useful to expose a DOM node to the parent—for example, to allow focusing it. To opt in, wrap your component definition into forwardRef():
공식문서에서 언제 forwardRef를 사용하는지 잘 설명되어 있습니다. 지금 같은 상황에서 forwardRef
는 완벽하게 들어맞습니다.
우리는 register
함수를 <input>
DOM 노드에 직접 사용해야 합니다. 이를 위해 forwardRef
를 사용 컴포넌트 내부의 <input>
DOM 노드를 컴포넌트에서 노출 시켜야 합니다.
import { forwardRef } from 'react';
import { useForm } from 'react-hook-form';
interface MyInputProps extends React.HTMLProps<HTMLInputElement> {
label: string;
}
const MyInput = forwardRef<HTMLInputElement, MyInputProps>(
({ label, ...rest }, ref) => {
return (
<label className='flex flex-col'>
<span>{label}</span>
<input ref={ref} {...rest} />
</label>
);
}
);
export default function Home() {
const { register, handleSubmit } = useForm();
const onSubmit = (data: any) => console.log(data);
return (
<main className='flex min-h-screen flex-col items-center justify-between p-24'>
<form onSubmit={handleSubmit(onSubmit)}>
<MyInput label='😀' {...register('좋아요')} />
<button type='submit'>Submit</button>
</form>
</main>
);
}
위와 같이 사용하면 1번 방법에서 겪었던 모든 문제를 해결할 수 있습니다. 이후 유효성검사를 위한 옵션들도 자연스럽게 넘겨줄 수 있습니다. 행복하네요.
감사합니다 헤매고 있던걸 해결했습니다