はじめに
react系のフォームバリデーションライブラリreact-hook-formのざっくり使い方です。
公式のドキュメントでは、
- 超軽量なパッケージ
- 再レンダリングを最小に押さえて、マウントの高速化
- フォームの値がローカル管理される為、他パッケージに依存しない
等々の利点が挙げられている。
formikとの比較
download
https://www.npmtrends.com/redux-form-vs-formik-vs-react-hook-formsize
formik
react-hook-form
API
useForm
useFormでいろいろなapiを受け取れる
const { register } = useForm({
mode: 'onSubmit', // | 'onBlur' | 'onChange'
reValidateMode: 'onChange', // onBlur | onSubmit 再度バリデーションされるタイミング
defaultValues: {},
validationSchema: {}, // スキーマレベルで Yup を使用してフォームバリデーションルールを適用
validateCriteriaMode: "firstErrorDetected",
submitFocusError: true, // エラーのある最初のフィールドがフォーカスされる
nativeValidation: false, // ブラウザバリデーションの活用
})
register
input/selectのRefとバリデーションルールをreact-hook-formに登録する
<input name="form1_1" defaultValue="test" ref={register} />
registerでバリデーションをかけられる。下記のコードではrequired, minLengthを5にせっていしている。
'1_2は必須です', '5桁以上必要です'はエラーメッセージ。
<input
name="form1_2"
ref={
register({
required: '1_2は必須です',
minLength : {
value: 5,
message: '5桁以上必要です'
}
})
}
/>
フィールドフォームネストして扱うこともできる
name | output |
---|---|
name="firstName" | { firstName: 'value' } |
name="firstName[0]" | { firstName: [ 'value' ] } |
name="name.firstName" | { name: { firstName: 'value' } } |
name="name.firstName[0]" | { name: { firstName: [ 'value' ] } } |
errors
errorsオブジェクトにはフォーム内の各フィールドのエラーオブジェクト。
react-hook-formにはエラーメッセージ用の表示にErrorMessageコンポーネントも用意されている。
import { useForm, ErrorMessage } from 'react-hook-form'
// ~~
<ErrorMessage errors={errors} name="form1_2" />
// ~~
watch
指定されたnameのinputを監視して、その値を返す。
defaultValue が定義されていない場合、watch の初回のレンダリングは register の前に呼び出されるため undefined を返しますが、 第2引数として defaultValue を設定して値を返すことができます。
ただし、引数として useForm で defaultValues が初期化された場合、 初回のレンダリングは defaultValues で指定された値を返します。
<p className="form1-watch-text">watch output: {watch('form1_1')}</p>
handleSubmit
フォームバリデーションを通るとデータを渡す。
// ~~
const onSubmit = (data: Object) => { console.table(data) };
// ~~
<input type="submit" />
// ~~
handleSubmit(async (data) => await fetchAPI(data))
import React from 'react';
import { useForm, ErrorMessage } from 'react-hook-form'
export default function Form1() {
const { register, handleSubmit, watch, errors } = useForm({ validateCriteriaMode: 'all' });
const onSubmit = (data: Object) => { console.table(data) };
return (
<div className="form form1">
<h1>Form1</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-section">
<span>1_1: </span>
<input name="form1_1" defaultValue="test" ref={register} />
<span className="sub-text">*watched</span>
</div>
<div className="form-section">
<span>1_2: </span>
<input
name="form1_2"
ref={
register({
required: '1_2は必須です',
minLength : {
value: 5,
message: '5桁以上必要です'
}
})
}
/>
<span className="sub-text">*required</span>
</div>
<div className="form1-watch">
<p className="form1-watch-text">watch output: {watch('form1_1')}</p>
</div>
<div className="errors">
<ErrorMessage errors={errors} name="form1_2" />
</div>
<input type="submit" />
</form>
</div>
)
};
controller
Controllerコンポーネント(UIコンポーネントライブラリと併せて使用するコンポーネント)用。
コンポーネント登録するためのメソッドが含まれている。
以下ではmaterial-uiを使用。
import React from 'react';
import { useForm, Controller, ErrorMessage } from 'react-hook-form';
import { TextField, Button } from "@material-ui/core";
export default function Form2() {
const { handleSubmit, errors, control } = useForm()
const onSubmit = (data: Object) => { console.table(data) };
return (
<div className="form form2">
<h1>Form2</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
as={<TextField />}
name="form2_1"
control={control}
rules={{ required: "必須です" }}
defaultValue=""
/>
<div className="errors">
<ErrorMessage errors={errors} name="form2_1" />
</div>
<Controller
as={<Button color="primary" ><span>送信</span></Button>}
name="submit"
control={control}
defaultValue=""
onClick={handleSubmit(onSubmit)}
/>
</form>
</div>
)
}
reset
フォーム内のvaluesとerrorsをリセットできる関数。
リセット時に値を渡すとデフォルトの値としてリセットできる。
import React, { useCallback } from 'react';
import { useForm, ErrorMessage } from 'react-hook-form'
type Reset = (values?: Record<string, any>) => void;
export default function Form3() {
const { register, handleSubmit, reset, errors }: ({
register: Function,
handleSubmit: Function,
reset: Reset,
errors: any,
}) = useForm();
const onSubmit = (data: Object) => { console.table(data) };
const onReset = useCallback(() => reset(), [reset])
const onDefaultReset = useCallback(() => reset({ first_name: 'ジョン', last_name: '万次郎' }), [reset])
return (
<div className="form form3">
<h1>Form3</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<span>姓:</span>
<input
name="last_name"
ref={register({ required: '姓は必須です。' })}
/>
<div className="errors">
<ErrorMessage errors={errors} name="last_name" />
</div>
<span>名:</span>
<input
name="first_name"
ref={register({ required: '名は必須です。' })}
/>
<div className="errors">
<ErrorMessage errors={errors} name="first_name" />
</div>
<input type="submit" />
<input
type="button"
onClick={onReset}
value="reset"
/>
<input
type="button"
onClick={onDefaultReset}
value="reset + default set"
/>
</form>
</div>
)
};
setError / clearError
inputのエラーを手動で設定したりクリアする。
setValue
値を動的に設定できる。
import React, { useCallback } from 'react';
import { useForm, ErrorMessage } from 'react-hook-form'
export default function Form4() {
const { register, handleSubmit, setError, clearError, errors, setValue } = useForm();
const onSubmit = (data: any) => {
const goodAnswer = 12 * 12;
if (parseInt(data.answer, 10) !== goodAnswer) return setError('answer', 'notMatch', '不正解です');
clearError('answer');
console.log('正解');
};
const onSetValue = useCallback(() => {
setValue('answer', 12 * 12);
}, [setValue]);
return (
<div className="form form4">
<h1>Form4</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<span>12 × 12 = </span>
<input
name="answer"
ref={register({ required: '入力してください' })}
/>
<div className="errors">
<ErrorMessage errors={errors} name="answer" />
</div>
<input
type="submit"
value="回答"
/>
<input
type="button"
value="諦める"
onClick={onSetValue}
/>
</form>
</div>
)
};
getValues
フォーム全体のデータを返す。
<input name="test" ref={register} />
<input name="test1" ref={register} />
<button
type="button"
onClick={() => {
const values = getValues()
console.log(values) // e.g: { test: [1, 2], test1: { data: '23' }
}}
>
GetValues
</button>
triggerValidation
バリデーションのトリガーを手動で設定できる。
<input name="lastName" ref={register({ required: true })} />
<button
type="button"
onClick={async () => {
triggerValidation("lastName");
}}
>
Trigger
</button>
unregister
inputにunregisterを適用すると、フォームデータに含まれなくなる。
これは、 useEffect でカスタム登録として input を登録 (register) し、 コンポーネントのアンマウント後に登録を解除する場合に便利です。
らしい。
formState
フォームの状態がオブジェクトで入っている。
name | description |
---|---|
dirty | 入力が行われた後trueになる |
isSubmitted | submitされた後trueになる |
isSubmitting | 送信中はtrue, 送信後falseになる |
touched | 操作されたnameが配列で入る |
submitCount | submitされた回数 |
isValid | errorがない場合true |
おわりです。込み入ったことは試していないのですが、記述が簡単な印象でした。