背景
開発部のチャブです。 最近所属しているチームでとあるウェブページのリニューアルをすることになりました。 現在のページが動いているスタックでは一定の技術的負債が溜まっていることもあり、せっかくの機会なのでReactで新規に開発する方針になりました。
今回のリニューアルの意図は入力項目を減らすことで設定を容易にすることです。対象となる元のページはフォームで15個ほどの入力項目があります。 すでにある設定項目はなくさず、必要な時にだけ入力できるように、項目を動的に増やすことができる必要があります。
そのようなフォームを実現するには、入力項目の追加と削除、そして表示されている項目一覧管理の実装が必要になりそうですが、React hook formで簡単にできたのでそちらを紹介します。
動的なフォーム
「任意の数のクラスを追加できるようにしたい」を例にします。 react hook formの useFieldArrayを用いることで簡単にフォームの入力項目の追加と削除を実現することができます。
const { control, register } = useForm(); const { fields, append, remove } = useFieldArray({ control, name: "classes", });
上記のようにフォームのcontrolから追加(append)と削除(remove)の関数を利用することができます。そして現在有効な入力項目一覧も管理され、その内容はfieldsから参照することも可能です。
例えば「クラスを追加」ボタンで入力できるクラスを増やす、もしくは「削除」ボタンで消したい項目をなくす、といったケースを想定します
<button type="button" onClick={() =>append({ name: "クラス名" })}>クラスを追加</button> <button type="button" onClick={() =>remove(index)}>削除</button>
そして現在有効な入力項目の管理もされており表示の実装は下記の通りになります。
return ( {fields.map((field, index) => ( <input key={field.id} {...register(`classes.${index}.value`)} /> ))} );
今ある項目の管理、追加、削除のロジック実装を一切行わず動的なフォームを実現することができます!
おまけ
車輪の再発明を行わずサクッと動的なフォームを実装出来てしまうReact hook formですが、react test frameworkでの単体テストとも相性が良く使いやすかったのでそちらも少しだけ紹介させてください。
例として、入力フォームへの項目追加と削除のテストです。特別な工夫をしなくても思い通りに項目の追加と削除のテストが行えます。
test("クラス項目を追加・削除できる", () => { const { getAllByText, getByText, getAllByRole } = render(<App />) expect(getAllByRole("textbox")).toHaveLength(1) fireEvent.click(getByText("クラスを追加")) expect(getAllByRole("textbox")).toHaveLength(2) fireEvent.click(getAllByText("削除")[0]) expect(getAllByRole("textbox")).toHaveLength(1) })