Reactでフォームの実装をしたことのある、もしくはこれから実装する皆さん。
React-hook-formをご存知ですか?
フォームの実装がとても楽になる便利なライブラリです。
この記事ではReact-hook-formの基本的な簡単な使い方と
実装例をソースコードとともに解説しています。
React-hook-formとは?
高性能で柔軟かつ拡張可能な使いやすいフォームバリデーションライブラリ。(引用)
従来のformライブラリに比べて、以下の特徴があります。1
・記述量が少ない
・レンダリングが少ない
・マウントが高速
・hooksで記述がシンプル
そして何より。。
バリデーションの実装が楽になります。
使い方
それではReact-hook-formの簡単な使い方を見てみましょう。
以下は公式デモのソースコードです。
import React from 'react'
import useForm from 'react-hook-form'
export default function App() {
const { register, handleSubmit, watch, errors } = useForm()
const onSubmit = data => { console.log(data) }
console.log(watch('example'))
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input name="example" defaultValue="test" ref={register} />
<input name="exampleRequired" ref={register({ required: true })} />
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
)
}
React-hook-formでは必要なメソッドやオブジェクトをuseFormから受け取って使用します。
以下の手順で実装します。
1. フィールドを登録する。
非制御コンポーネント (Uncontrolled Components) をフックに登録(register) し、フォームフィールドの値を検証と収集できるようにする(引用)
登録したいフィールドにname="uniqueName"
とref={register}
を加えます。
<input name="example" defaultValue="test" ref={register} />
2. バリデーションとエラー文言を設定する。
registerメソッドにバリデーションを渡し、
バリデーション時にエラーが発生するとerrorsオブジェクトに
先ほど加えたnameをkeyとしたエラーメッセージを割り当てられます。2
<input name="exampleRequired" ref={register({ required: true })} />
{errors.exampleRequired && <span>This field is required</span>
上記の例のrequired
はバリデーションの際に必須入力を求めます。
バリデーションは上記の他に最大文字数、最小文字数なども設定でき、
さらに正規表現やバリデーション関数を渡すこともできます!
実装例
では実際に以下のようなフォームを実装してみます。
・各フォームごとに入力後バリデーションする
・バリデーションエラーの場合はエラーメッセージを表示する
・全てのフォームが正しく入力されている場合のみsubmitボタンを押せるようにする
hooksのみで実装してみると。。。(長いので読む必要なし)
import * as React from 'react';
interface FormData {
title: string;
author: string;
}
interface FormValidationResults {
title: boolean;
author: boolean;
}
interface ErrorMessage {
title: string;
author: string;
}
const SomeForms: React.FC = () => {
const [values, setValues] = React.useState<FormData>({
title: '',
author: ''
});
const [validationResults, setValidationResults] = React.useState<
FormValidationResults
>({
title: false,
author: false
});
const [errorMessages, setErrorMessages] = React.useState<ErrorMessage>({
title: '',
author: ''
});
const handleChange = (name: keyof FormData) => (
event: React.ChangeEvent<HTMLTextAreaElement>
) => {
const newValues = { ...values, [name]: event.target.value };
setValues(newValues);
validate(newValues, name);
};
const validate = (values: FormData, name: keyof FormValidationResults) => {
switch (name) {
case 'title':
titleValidation(values[name]);
break;
case 'author':
authorValidation(values[name]);
break;
}
};
const titleValidation = (value: string): void => {
if (value.length < 1 || value.length > 20) {
setValidationResults({ ...validationResults, title: false });
setErrorMessages({
...errorMessages,
title: 'タイトル名は1文字以上、20文字以下でなければなりません。'
});
} else {
setValidationResults({ ...validationResults, title: true });
setErrorMessages({ ...errorMessages, title: '' });
}
};
const authorValidation = (value: string): void => {
if (value.length < 1 || value.length > 20) {
setValidationResults({ ...validationResults, author: false });
setErrorMessages({
...errorMessages,
author: '作者名は1文字以上、20文字以下でなければなりません。'
});
} else {
setValidationResults({ ...validationResults, author: true });
setErrorMessages({ ...errorMessages, author: '' });
}
};
return (
<div>
<h2>タイトル名</h2>
<textarea
name='title'
value={values.title}
onChange={handleChange('title')}
/>
{errorMessages.title && <span>{errorMessages.title}</span>}
<h2>作者名</h2>
<textarea
name='author'
value={values.author}
onChange={handleChange('author')}
/>
{errorMessages.author && <span>{errorMessages.author}</span>}
<button
disabled={
validationResults.title && validationResults.author ? false : true
}
>
送信する
</button>
</div>
);
};
export default SomeForms;
。。。長い。。改行があるとはいえ100行強あります。
useStateで以下を管理しています。。。長い。
・フィールドの値
・エラーメッセージ
・バリデーションがvalidかどうか
React-hook-formで実装
import * as React from 'react';
import useForm from 'react-hook-form';
interface FormData {
title: string;
author: string;
}
const OtherForms: React.FC<{}> = () => {
const { register, handleSubmit, errors, formState } = useForm<FormData>({
mode: 'onChange'
});
const onSubmit = (data: FormData): void => console.log(data);
return (
<div>
<h2>タイトル名</h2>
<form onSubmit={handleSubmit(onSubmit)}>
<textarea
name='title'
ref={register({ required: true, maxLength: 20 })}
/>
{errors.title && '作者名は1文字以上、20文字以下でなければなりません。'}
<h2>作者名</h2>
<textarea
name='author'
ref={register({ required: true, maxLength: 20 })}
/>
{errors.author && '作者名は1文字以上、20文字以下でなければなりません。'}
<button disabled={!formState.isValid}>送信する</button>
</form>
</div>
);
};
export default OtherForms;
なんと37行!(しかもフォームの結果をconsole.logで出力している)
デモの実装例にはなかった2つのメソッドorオブジェクトを追加して実装しています。
・formState
フォームの状態に関する情報が含まれているオブジェクト。
formState.isValidはフィールドにエラーがない状態かどうかをbooleanで表しています。
・handleSubmit
バリデーションに成功するとフォームのデータを渡してくれるメソッド。
補足
バリデーションのタイミングはオプションで指定することができます。
今回は各フォームの入力ごとにバリデーションしたいので、
useFormに{mode: 'onChange'}
を渡しています。
パフォーマンスの観点ではレンダリングが増えるので推奨されてはいないようです。
-
バリデーションのみの登録とバリデーションとエラーメッセージをセットで登録することもできます。https://react-hook-form.com/jp/api/#register ↩