React Hook Formで動的フォームを作ってみた
今回は、React Hook Formで動的フォームを作る方法について共有します!
今年に入るまで、業務でもReact Hook Formを扱ったことはなかったのですが、4月から参画しているプロジェクトがきっかけで、プライベートでも使用してみることにしました。
通常のフォーム作成だけでも良かったのですが、フィールドの数が増減する「動的フォーム」の実装に憧れて、今回挑戦してみました🔥
※ところどころany型で逃げてるのは許してね!!12月だし、年末近いし...笑
そもそもReact Hook Formとは
React Hook Formとは、Reactでフォームを簡単に扱えるライブラリです。
入力値の取得やバリデーションなどを行うことができます。
本記事では、そんなReact Hook Formの基本の扱い方から動的フォームの作り方までお伝えできたら良いなと思っています!
通常のフォームの作り方
まずは、通常のフォームの作り方から見ていきます。
以下の画像のような、入力フィールドが1つだけ存在するシンプルなフォームを作ってみます。
先にこのフォームを実装したコード全体を見せます。(import
周りは端折ってます。)
const SampleForm = () => {
const { register, handleSubmit, watch, reset, formState: { errors } } = useForm();
const watchAllVal = watch(); // 入力値を監視
// フォーム送信
const submitForm = (data: any): void => {
console.log(data);
// フォームを空にする。
reset();
};
return (
<div>
<h1>基本のフォーム</h1>
<form>
<div>
<label>title:</label>
<input {...register('title', { pattern: /[A-Za-z]/ })} />
{errors.title && <span>文字を入力してください!</span>}
</div>
<input type='submit' onClick={handleSubmit(submitForm)} />
</form>
<div>
{/* 現在入力されている値 */}
<p>title:{watchAllVal.title}</p>
</div>
</div>
);
};
export default SampleForm;
JSX部分について
まず、JSX部分に注目してみます。
return (
<div>
<h1>基本のフォーム</h1>
<form>
<div>
<label>title:</label>
<input {...register('title', { pattern: /[A-Za-z]/ })} />
{errors.title && <span>アルファベットを入力してください!</span>}
</div>
<input type='submit' onClick={handleSubmit(submitForm)} />
</form>
<div>
{/* 現在入力されている値 */}
<p>title:{watchAllVal.title}</p>
</div>
</div>
);
そもそも<input>
要素を設置することで値の入力をすること自体はできます。
ただ、値の入力・取得を行えるようにするには、register
関数を使う必要があります。
このregister
関数を実行することで、フォームの変更に応じてevent.target.value
などで値を取得できるonChange
イベントやname
属性の設定などが行われています。
公式ドキュメントのregisterを参照
...register(フィールドのname)
というように使います。(form
関係の要素に、name属性がありますよね。)
また、第2引数に正規表現(pattern
)や、最大文字数(maxLength
)等をオブジェクトとして渡すことで、バリデーションを行うことができます。
今回の例では、アルファベット以外が入力されると、エラーメッセージが表示されるようにしています。
<input {...register('title', { pattern: /[A-Za-z]/ })} />
{errors.title && <span>アルファベットを入力してください!</span>}
試しに、アルファベットではなく数字を入れてみました。
エラーメッセージ(「アルファベットを入力してください!」)が表示されています。
watch
関数を使うことで入力値を全て監視することができます。
const watchAllVal = watch(); // 入力値を監視
〜中略〜
{/* 現在入力されている値 */}
<p>title:{watchAllVal.title}</p>
入力値はオブジェクトになっており、watchAllVal.title
というように、フィールドのname
をキー名として指定して、現在の入力状況を取得することが可能です。画像だと伝わりにくいと思うので、機会があれば試してみてください🙇♀️
<input>
要素だけでなく、<select>
要素も同じように取り扱うことができます。
return (
<div>
<h1>基本のフォーム</h1>
<form>
<div>
<label>title:</label>
<input {...register('title', { pattern: /[A-Za-z]/ })} />
{errors.title && <span>アルファベットを入力してください!</span>}
</div>
{/* select要素 */}
<div>
<label>ラベル:</label>
<select {...register('select')}>
<option defaultChecked>選択してください</option>
<option value='aaa'>aaa</option>
<option value='bbb'>bbb</option>
<option value='ccc'>ccc</option>
</select>
{errors.select && <span>どれか1つ以上チェックを入れてください!</span>}
</div>
<input type='submit' onClick={handleSubmit(submitForm)} />
</form>
<div>
{/* 現在入力されている値 */}
<p>title:{watchAllVal.title}</p>
<p>select:{watchAllVal.select}</p>
</div>
</div>
);
基本のフォーム送信について
それでは、フォーム送信部分について見ていきます。
フォーム送信は、JSXで見るとこちらで実行されていました。
<input type='submit' onClick={handleSubmit(submitForm)} />
handleSubmit
関数でバリデーションを実行し、submitForm
関数(関数名は任意です。)が入力値を受け取って実行されます。
私が用意したsubmitForm
関数は、以下のような内容になっています。
// フォーム送信
const submitForm = (data: any): void => {
console.log(data);
// フィールドを空にする。
reset();
};
reset
関数を使うことで、フォーム送信を行なった後、フィールドを空にすることができます。
引数として渡ってきた入力値を使って、API実行してデータベースに追加する...みたいな使い方もできますね!
動的フォームの作り方
React Hook Formの基本的な扱い方については、わかって頂けたかなと思います。
ここからは、本題である、動的フォームについてお伝えしていきたいと思います。
以下の画像のような、入力フィールドを先頭や後に追加できるような動的フォームを作ってみます。
先にこのフォームを実装したコード全体を見せます。(import
周りは端折ってます。)
type AddTasksParamType = {
sample: string;
};
const SampleDynamicForm = () => {
const { register, handleSubmit, control, reset } = useForm({
defaultValues: {
sample: [
{
title: ''
}
]
}
});
const { fields, prepend, append, remove } = useFieldArray({
control,
name: 'sample'
});
const onSubmit = (data: any): void => {
console.log(data);
// フォームを空にする。
reset();
};
return (
<div>
<h1>動的フォーム</h1>
<form>
<button type="button" onClick={() => prepend({title: ''})}>
<FontAwesomeIcon icon={faPlus} />先頭に追加
</button>
{fields.map((field: any, index: number) => (
<div key={field.id}>
<label>タスクNo.{index}:</label>
<div>
<input {...register(`sample.${index}.title`)} placeholder='ここにタスク名を入力してください' />
<FontAwesomeIcon icon={faCircleXmark} className='removeFormIcon' onClick={() => remove(index)} />
</div>
</div>
))}
<button type="button" onClick={() => append({title: ''})}>
<FontAwesomeIcon icon={faPlus} />後ろに追加
</button>
<div>
<BackToHomeButton />
<SubmitButton onClick={handleSubmit(onSubmit)} text='登録' />
</div>
</form>
</div>
);
};
export default SampleDynamicForm;
動的フォームを作成するためには、React Hook Formで提供されているuseFieldArray
フックを利用します。
const { fields, prepend, append, remove } = useFieldArray({
control,
name: 'sample'
});
fields
には、デフォルト値が入ったオブジェクトを要素する配列になっています。
入力フィールドが増減することで、この配列の要素も増減します。
useFieldArray
フックに渡すオブジェクトの中に、name
プロパティがありますが、これは、入力値を格納するオブジェクトのキー名となります。
JSX部分について
ここでも、JSX部分に着目してみます。
return (
<div>
<h1>動的フォーム</h1>
<form>
<button type="button" onClick={() => prepend({title: ''})}>
<FontAwesomeIcon icon={faPlus} />先頭に追加
</button>
{/* 配列fieldsを展開する */}
{fields.map((field: any, index: number) => (
<div key={field.id}>
<label>タスクNo.{index}:</label>
<div>
<input {...register(`sample.${index}.title`)} placeholder='ここにタスク名を入力してください' />
<FontAwesomeIcon icon={faCircleXmark} className='removeFormIcon' onClick={() => remove(index)} />
</div>
</div>
))}
<button type="button" onClick={() => append({title: ''})}>
<FontAwesomeIcon icon={faPlus} />後ろに追加
</button>
<ButtonsArea>
<BackToHomeButton />
<SubmitButton onClick={handleSubmit(onSubmit)} text='登録' />
</ButtonsArea>
</form>
</div>
);
入力フィールドが要素となっている配列fields
をmap
関数で展開していくことで、入力フィールドを表示しています。
入力フィールドを追加〜append/prepend〜
それでは、入力フィールドの増減について見ていきます。
まずはよくある、後ろに追加するパターンです。
append
関数を使用します。
<button type="button" onClick={() => append({title: ''})}>
<FontAwesomeIcon icon={faPlus} />後ろに追加
</button>
append
関数の第1引数に、追加するフィールドの状態をオブジェクトとして渡します。ここでは、空の状態で追加したいので、空のままで渡しています。
ここでもし、空文字ではなく何か値を入れた状態で渡すと、その値が既に入力されたフィールドが追加されます。
append
関数が実行されることで、配列fields
の要素も増加します。そのため、配列fields
が展開する要素が増えるので、表示される入力フィールドも増えていきます。
また、先頭に追加することもできます。
prepend
関数を使用します。
<button type="button" onClick={() => prepend({title: ''})}>
<FontAwesomeIcon icon={faPlus} />先頭に追加
</button>
使う関数が、prepend
関数になったくらいで、構文的にはappend
関数と変わりありません。
もちろん、prepend
関数が実行されることで、配列fields
の要素も増加します。
どちらを使用するかは、フィールドを「どこに追加したいか」によって異なってきます。
フォーム送信について
フォーム送信については、基本のフォーム送信と変わらないので、前出の基本のフォーム送信についてをご参照下さい。
ちなみに、動的フォームでフォーム送信で受け取る入力値は、以下のような形式になっています。
useFieldArray
フックのname
で指定したsample
をキー名とするオブジェクトが取得できます。フィールドの入力値は、配列の要素となっています。
感想
フォームを扱うことは業務でも多々あるかと思います。
React Hook Formを使うことで、入力した値の取得などが簡単に行えるようになります。
動的フォームもuseFieldArray
フックを使用することで簡単に作成することができてしまいます。
「車輪の再発明をしたい!」という気分の時以外には、使うと便利だな〜と思います!
認識の誤り・補足などがあれば、是非、コメントして頂けますと助かります〜!
長文お読み下さり、ありがとうございました!
Discussion
記事の内容を生かしつつデモ作ってみました。
/todo
ページがデモページになります。簡単ですが、以上です。
nap5さん
本記事を参考にして下さり、ありがとうございます🙇♀️
お役に立てたのであれば、とても嬉しいです!!