react-hook-form(1) : Performant, flexible and extensible forms with easy-to-use validation
리액트에서 폼 태그를 더 용이하게 사용할 수 있다는.. 도구.. 한번 사용해보겠습니다.
기존에 들어만 봤었던 formik이라는 라이브러리도 있었는데, 아래 블로그를 보고 react-hook-form을 사용하기로 했다.
https://blog.songc.io/react/react-form-control/
https://react-hook-form.com/get-started
Get Started부터 보고 시작해보려고 하는데, 일단 register, handleSubmit 등에 대한 설명은 전혀 없다.
import React from "react";
import { useForm, SubmitHandler } from "react-hook-form";
type Inputs = {
example: string,
exampleRequired: string,
};
export default function App() {
const { register, handleSubmit, watch, formState: { errors } } = useForm<Inputs>();
const onSubmit: SubmitHandler<Inputs> = data => console.log(data);
console.log(watch("example")) // watch input value by passing the name of it
return (
/* "handleSubmit" will validate your inputs before invoking "onSubmit" */
<form onSubmit={handleSubmit(onSubmit)}>
{/* register your input into the hook by invoking the "register" function */}
<input defaultValue="test" {...register("example")} />
{/* include validation with required or other standard HTML validation rules */}
<input {...register("exampleRequired", { required: true })} />
{/* errors will return when field validation fails */}
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
);
}
만약 라이브러리를 사용하지 않고 form태그를 사용하려면, input마다 attribute를 다 설정해줘야 하는데, 일단 그건 편하다.
React Hook Form의 키 컨셉은 우리의 컴포넌트를 hook에 register하는 것이다.
register를 통해 input의 value에 대한 유효성 검사나 제출이 가능해진다.
각각의 input은 등록할 때 이름(ex. 아래 예제 코드의 "firstName", "gender")이 필요하다.
import React from "react";
import ReactDOM from "react-dom";
import { useForm, SubmitHandler } from "react-hook-form";
enum GenderEnum {
female = "female",
male = "male",
other = "other"
}
interface IFormInput {
firstName: String;
gender: GenderEnum;
}
export default function App() {
const { register, handleSubmit } = useForm<IFormInput>();
const onSubmit: SubmitHandler<IFormInput> = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>First Name</label>
{/* 이름! */}
<input {...register("firstName")} />
<label>Gender Selection</label>
<select {...register("gender")} >
<option value="female">female</option>
<option value="male">male</option>
<option value="other">other</option>
</select>
<input type="submit" />
</form>
);
}
React Hook Form의 Validation
React Hook Form은 form 유효성 검사를 위한 기존 HTML 유효성 검사 표준에 맞춰 form의 유효성 검사를 쉽게 만든다.
예시는 다음과 같고,
import React from "react";
import { useForm, SubmitHandler } from "react-hook-form";
interface IFormInput {
firstName: string;
lastName: string;
age: number;
}
export default function App() {
const { register, handleSubmit } = useForm<IFormInput>();
const onSubmit: SubmitHandler<IFormInput> = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName", { required: true, maxLength: 20 })} />
<input {...register("lastName", { pattern: /^[A-Za-z]+$/i })} />
<input type="number" {...register("age", { min: 18, max: 99 })} />
<input type="submit" />
</form>
);
}
지원하는 유효성 검사 규칙은 다음과 같다:
- required
- min
- max
- minLength
- maxLength
- pattern
- validate
더 자세하게 알고 싶으면 register section을 보라고 한다. 그래서 Get Started 순서와 다르게 register 섹션을 먼저 보자.
그래서 register가 뭐냐
register: (name: string, RegisterOptions?) => ({ onChange, onBlur, name, ref })
register 메서드를 사용하면 React Hook Form에 input이나 select 엘리먼트를 등록하고, 유효성 검사 규칙을 등록할 수 있다. 모든 유효성 검사 규칙은 (위에서 말했듯이) HTML 표준을 따르고, 또한 유효성 검사 메서드를 커스터마이징 할 수 있다.
input의 이름을 인자로 register 함수를 호출하면, 다음의 메서드들을 반환받는다.
Name | Type | Description |
onChange | ChangeHandler | onChange prop to subscribe the input change event |
onBlur | ChangeHandler | onBlur prop to subscribe the input blur event |
ref | React.Ref<any> | input reference for hook form to register |
name | string | input's name being registered |
다음과 같이 쓸 수 있는 것이다.
const { onChange, onBlur, name, ref } = register('firstName');
// include type check against field path with the name you have supplied.
<input
onChange={onChange} // assign onChange event
onBlur={onBlur} // assign onBlur event
name={name} // assign name prop
ref={ref} // assign ref prop
/>
그리고 위 코드는, 다음과 똑같다
<input {...register('firstName')} />
내가 궁금한 건 register의 또 다른 인자인 RegisterOptions인데, 내가 하고 싶은 건 단순히 input의 최대 길이나 input의 패턴 같은, input의 타입이 text일 때 할 검사가 아니라 input의 타입이 file일 때, 파일 크기나 확장자, 여러개의 파일에 대한 용량 검사 같은 게 하고 싶은 것이기 때문이다.
이 페이지에 required나 pattern, maxLength 등 제공하는 옵션들에 대한 자세한 설명과 예시가 나와있다.
나는 그 중에서 validate라는 옵션이 눈에 띄었는데, 유효성 검사를 위한 콜백을 인자로 넘겨줄 수 있다고 한다. 단순히 콜백 함수만 넘기는 게 아니라 콜백함수로 이뤄진 객체도 인자로 넘길 수 있다. 페이지에 나와있는 예제 코드는 다음과 같다.
<input
{...register("test", {
validate: value => value === '1'
})}
/>
// object of callback functions
<input
{...register("test1", {
validate: {
positive: v => parseInt(v) > 0,
lessThanTen: v => parseInt(v) < 10,
checkUrl: async () => await fetch(),
}
})}
/>
다른 옵션들은 페이지에서 확인하고, 잠시 register를 사용할 때 유의해야할 규칙들(역시 공식 문서에 잘 정리돼있지만)을 짚고 넘어가자.
Rules
- register의 첫 번째 인자 name은 꼭 넣어야 하고(required), 다른 name과 겹치면 안 된다(unique). Input name은 점과 괄호를 모두 허용한다. 점과 괄호를 사용하면 중첩된 form field를 쉽게 만들 수 있다.
register("firstName") {firstName: 'value'}
register("name.firstName") {name: { firstName: 'value' }}
register("name.firstName.0") {name: { firstName: [ 'value' ] }}
- name은 숫자로 시작해서는 안 되고, key name도 숫자가 될 수 없다.(예시를 작성해봐야 알 것 같다)
- 타입스크립트 사용의 일관성을 위해서 dot syntax를 사용하고 있고, 그래서 배열 form value에 []를 사용할 수 없다.
register('test.0.firstName'); // ✅
register('test[0]firstName'); // ❌
- disabled input은 정의되지 않은 form value가 될 수 있다. 따라서 입력을 못하게 하려면 input에 readOnly라는 attribute를 설정해주거나, <filedSet /> 전체를 disabled로 만들면 된다.(예제 코드 전체는 여기서 확인)
...
<form onSubmit={handleSubmit(onSubmit)}>
<input readOnly {...register("firstName")} placeholder="First Name" />
<fieldset disabled>
<input {...register("lastName")} placeholder="First Name" />
</fieldset>
<input type="submit" />
</form>
...
- 배열로 된 field를 생성하려면, input name에 점과 숫자가 와야 된다. ex) test.0.data
- 매 렌더링마다 이름을 변경하면 새 input이 등록된다. 따라서 input의 name은 static한 게 좋다.
- input value와 reference는 언마운트 시에 제거되지 않으므로, unregister 해줘야 한다.
- 각각의 register option은 undefined 또는 {}로 제거할 수 없다. 대신 각각의 attribute를 업데이트 할 수 있다.
register('test', { required: true });
register('test', {}); // ❌
register('test', undefined); // ❌
register('test', { required: false }); // ✅
한글로 번역하면서도 잘 모르겠는 부분도 있고, 페이지 중에서 옮기지 않은 부분이 있으므로 호오오옥시나 이 글을 보신다면 꼭 공식문서를 참고해주세요..
그래서 register 함수는 어디서 어떻게 불러와야 하는 건가 하는 의문이 생길 수 있다.
register는 React Hook Form이 제공하는 useForm()이라는 훅을 호출함으로써 사용할 수 있다.
import {useForm} from 'react-hook-form';
...
type FormInputs = {
firstName: string;
lastName: string;
};
// 이르케!
const { register } = useForm<FormInputs>({
mode: 'onSubmit',
reValidateMode: 'onChange',
defaultValues: {},
resolver: undefined,
context: undefined,
criteriaMode: "firstError",
shouldFocusError: true,
shouldUnregister: false,
delayError: undefined
})
...
useForm()에 대해서는 다른 게시글에 정리해야지..
registerOptions의 validate로 내가 원하는 파일 검사를 할 수 있는지 확인해보러가야게따!