とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど
各種SNSのフォローもよろしくお願いいたします。

ReactでReact hook formを使ってみよう(可変フォーム編)

こんにちは。 アドベントカレンダー11日目です。 今年はフロントエンドまわりであれこれ開発することが多かったので、振り返りを兼ねてこれからフロントエンド開発を始める方向けに入門編としてお送りしています。 最終日の25日には何かアプリケーションができていることが目標です。

↓最初から読み始めたい方はこちらか↓

devops-blog.virtualtech.jp

おさらい

Visual Studio Codeを使ったReactの開発環境の構築(第1回)から始まり、CSSフレームワークの1つであるtailwindcssを使ったUI開発(第2回)やコンポーネント化(第3回)について話しています。

開発サイクルを効率的に回していくための、サポート的や役割のツールとして、フォーマッター(第4回)、Linter(第5回)、バージョン管理(第6)について学んできました。

第7回からReactに戻り、状態管理やカスタムフックを使ったライブラリの1つであるReact hook form(第8)について学びました。

第9回第10回では、週末コラムとしてVisual Studio Codeの設定について紹介しました。

今回のはなし

第8回で紹介したReact hook formを使ったフォーム操作について、もう少し詳しく見ていきます。 前回までは、固定のフォームを作成していましたが、今回は追加ボタンや削除ボタンを押す度に入力項目が増減する可変フォームを作成していきます。

devops-blog.virtualtech.jp

使い方

第8回の内容が完了していることを前提に進めていきます。まだの方は、第8回を参考にしてください。 では、始めていきましょう。 今回は特に追加パッケージのインストールは必要ありません。

完成イメージ

準備

始めに前回のコードを少しお掃除します。次のコードを削除してください。

1カ所目

type Inputs = {
  name: string;
  email: string;
- count: number;
};

2カ所目

const defaultValues: Inputs = {
  name: "",
  email: "",
- count: 1,
};

3カ所目

...
-         <div>
-           <label>参加人数</label>
-           <input
-             type="number"
-             className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
-             placeholder="1"
-             {...register("count", { required: true })}
-           />
-         </div>
...

参加者名簿を追加

参加人数を入力するところから、参加者名簿を追加するように変更していきます。可変フォームには、useFieldArrayを使って実装します。useFieldArrayは、フォームの入力項目を配列で管理するためのフックです。useFieldArrayを使うことで、フォームの入力項目を動的に増減させることができます。

export default function Home() {
  const {
    register,
+   control,
    handleSubmit,
    formState: { isValid },
  } = useForm<Inputs>({ defaultValues });

+ const { fields, append, remove } = useFieldArray({
+   control,
+   name: "attendees",
+ });
  ...
}

次に参加者名簿の入力フォームを追加します。

export default function Home() {
...
+         <div>
+           <label>参加者名簿</label>
+           {fields.map((field, index) => (
+             <div key={field.id} className="flex">
+               <div>
+                 <input
+                   type="text"
+                   className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
+                   placeholder="山田 太郎"
+                   {...register(`attendees.${index}.name`, { required: true })}
+                 />
+               </div>
+               <div>
+                 <button
+                   type="button"
+                   className="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg"
+                   onClick={() => {
+                     remove(index);
+                   }}
+                 >
+                   削除
+                 </button>
+               </div>
+             </div>
+           ))}
+           <div>
+             <button
+               type="button"
+               className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg"
+               onClick={() => append({ name: "" })}
+             >
+               参加者を追加
+             </button>
+           </div>
+         </div>
          <div>
            <button
              type="submit"
              disabled={!isValid}
              className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg disabled:opacity-50"
            >
              送信
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

{fields.map((field, index) => ( ... )}の部分で配列の要素数分だけ入力フォームを表示する処理を記述しています。

入力フィールドを新しく追加したい場合は、append(...)を呼び出して追加します。追加したフィールドを削除したい場合は、remove(...)を呼び出します。

入力フォームの部分は{...register(attendees.${index}.name, { required: true })}のようになり、indexを渡すことで、配列の要素を指定しています。

${data.count}は、${data.attendees.length}に変更します。

const onSubmit = (data: Inputs) => {
- const message = `こんにちは、${data.name}さん!\n受付しました。当日のお越しをお待ちしております。\nメールアドレス: ${data.email}\n参加人数: ${data.count}`;
+ const message = `こんにちは、${data.name}さん!\n受付しました。当日のお越しをお待ちしております。\nメールアドレス: ${data.email}\n参加人数: ${data.attendees.length}`;
  alert(message);
};

コード全体

クリックして全体を表示

"use client";

import { useFieldArray, useForm } from "react-hook-form";

type Attendee = {
  name: string;
};

type Inputs = {
  name: string;
  email: string;
  attendees: Attendee[];
};

const defaultValues: Inputs = {
  name: "",
  email: "",
  attendees: [],
};

export default function Home() {
  const {
    register,
    control,
    handleSubmit,
    formState: { isValid },
  } = useForm<Inputs>({ defaultValues });

  const { fields, append, remove } = useFieldArray({
    control,
    name: "attendees",
  });

  const onSubmit = (data: Inputs) => {
    const message = `こんにちは、${data.name}さん!\n受付しました。当日のお越しをお待ちしております。\nメールアドレス: ${data.email}\n参加人数: ${data.attendees.length}`;
    alert(message);
  };

  return (
    <div className="p-5 container mx-auto md">
      <div>
        <p className="text-2xl">参加受付</p>
      </div>
      <div>
        <form onSubmit={handleSubmit(onSubmit)}>
          <div>
            <label className="block mb-2 text-sm">名前</label>
            <input
              type="text"
              className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
              placeholder="山田 太郎"
              {...register("name", { required: true })}
            />
          </div>
          <div>
            <label>メールアドレス</label>
            <input
              type="email"
              className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
              placeholder="example@example.com"
              {...register("email", { required: true })}
            />
          </div>
          <div>
            <label>参加者名簿</label>
            {fields.map((field, index) => (
              <div key={field.id} className="flex">
                <div>
                  <input
                    type="text"
                    className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                    placeholder="山田 太郎"
                    {...register(`attendees.${index}.name`, { required: true })}
                  />
                </div>
                <div>
                  <button
                    type="button"
                    className="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg"
                    onClick={() => {
                      remove(index);
                    }}
                  >
                    削除
                  </button>
                </div>
              </div>
            ))}
            <div>
              <button
                type="button"
                className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg"
                onClick={() => append({ name: "" })}
              >
                参加者を追加
              </button>
            </div>
          </div>
          <div>
            <button
              type="submit"
              disabled={!isValid}
              className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg disabled:opacity-50"
            >
              送信
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

おわりに

今回は、React hook formを使って可変フォームを作成してみました。画面に動きが出てくるとWebアプリケーションを作ってる感じが出てきて、楽しくなってきますね。 React hook formは意外と便利なので、ぜひ使ってみてください。