BLOG

AstroでPreactとZodを使ってお問い合わせフォームのバリデーションを作成する方法

AstroでPreactとZodを使ってお問い合わせフォームのバリデーションを作成する方法

先日、Astroを使ったサイトでPreactとZodを用いてお問い合わせフォームのバリデーションを実装する機会があったので、作成手順を紹介します。Zodとはスキーマ定義やバリデーションのためのJavaScriptライブラリです。Preactは、Reactと同じような機能を提供しながら、Reactに比べてファイルサイズが小さく、パフォーマンスが高いことが特徴のJavaScriptのフロントエンドライブラリです。

1. 必要なライブラリのインストール

このフォームを作成するにあたり、preactとzodという2つのライブラリが必要になります。以下のコマンドを使用してインストールしてください。

npm install preact zod

2. フォームの作成

まず、フォームのコンポーネントを作成します。フォームの要素を定義し、必要な値をstateで管理します。

contact-form.jsx

import { useState } from "preact/hooks";
import { z } from "zod";

export const ContactForm = () => {
  // バリデーションスキーマの定義
  const validationSchema = z.object({
    name: z.string().min(1, { message: "お名前をご入力ください" }),
    email: z
      .string()
      .min(1, { message: "メールアドレスをご入力ください" })
      .email({ message: "正しいメールアドレスをご入力ください" }),
    message: z
      .string()
      .min(1, { message: "お問い合わせの詳細をご入力ください" }),
  });

  // フォームの値を管理するstate
  const [formValues, setFormValues] = useState({
    name: null,
    email: null,
    message: null,
  });

  // フォームのバリデーション結果を取得
  const formValidateResult = validationSchema.safeParse(formValues);

  // フォームのバリデーションが成功しているかどうかを取得
  const isAllValid = formValidateResult.success;

  // バリデーションエラーがある場合はエラーメッセージを取得
  const errors = isAllValid ? undefined : formValidateResult.error.format();

  // フォームの送信処理
  const submitForm = (event) => {
    if (!isAllValid) {
      event.preventDefault();
      return;
    }

    // フォーム送信先のURLを指定
    const formUrl = "送信先URL";
    const form = document.getElementById("form");
    form.action = formUrl;
  };

  // フォームを描画
  return (
    <div>
      <div>
        <div>
          <form method="post" id="form" onSubmit={submitForm}>
            <div>
              <div>
                <div>
                  <span>お名前</span>
                  <span>
                    必須
                  </span>
                </div>
                <div>
                  <span>
                    <input
                      type="text"
                      name="お名前"
                      placeholder="お名前をご入力ください"
                      onInput={(e) =>
                        setFormValues({ ...formValues, name: e.target.value })
                      }
                    />
                    {formValues.name !== null && errors.name && (
                      <span>
                        {errors.name._errors[0]}
                      </span>
                    )}
                  </span>
                </div>
              </div>
              <div>
                <div>
                  <span>メールアドレス</span>
                  <span>
                    必須
                  </span>
                </div>
                <div>
                  <span>
                    <input
                      type="text"
                      name="メールアドレス"
                      placeholder="メールアドレスをご入力ください"
                      onInput={(e) =>
                        setFormValues({ ...formValues, email: e.target.value })
                      }
                    />
                    {formValues.email !== null && errors.email && (
                      <span>
                        {errors.email._errors[0]}
                      </span>
                    )}
                  </span>
                </div>
              </div>
              <div>
                <div>
                  <span>お問い合わせ詳細</span>
                  <span>
                    必須
                  </span>
                  <div>
                    <span>
                      <textarea
                        name="お問い合わせ詳細"
                        placeholder="お問い合わせの詳細をご入力ください"
                        onInput={(e) =>
                          setFormValues({
                            ...formValues,
                            message: e.target.value,
                          })
                        }
                      ></textarea>
                      {formValues.message !== null && errors.message && (
                        <span>
                          {errors.message._errors[0]}
                        </span>
                      )}
                    </span>
                  </div>
                </div>
              </div>
              <div>
                <div>
                  <button
                    disabled={!isAllValid ? true : false}
                    type="submit"
                    name="送信する"
                    value=""
                  >
                    送信する
                  </button>
                </div>
              </div>
            </div>
          </form>
        </div>
      </div>
    </div>
  );
};

以下の箇所は、フォームの入力欄に入力された文字列を収集し、その値をformValuesオブジェクトのnameプロパティに保存するための処理を行っています。具体的には、以下のことを行っています。

  • <input>要素にonInputというイベントリスナーを設定して、入力欄に文字が入力されるたびに、onInputイベントが発生します。
  • このイベントが発生するたびに、setFormValues()関数が呼び出され、formValuesオブジェクトのnameプロパティの値が、入力欄に入力された文字列に更新されます。
onInput={(e) =>
  setFormValues({ ...formValues, name: e.target.value })
}
onBlur={(e) =>
  setFormValues({ ...formValues, name: e.target.value })
}

そして、以下の箇所で更新された値をもとに、エラーがある場合、かつ初期表示ではない場合にエラーメッセージが表示されます。

{formValues.name !== null && errors.name && (
    <span>
       {errors.name._errors[0]}
  </span>
)}

以下は必須項目のバリデーションが全て成功していない場合にdisabled属性をtrueにしてフォームを送信できないようにしています。

<button
    disabled={!isAllValid ? true : false}
  type="submit"
  name="送信する"
  value=""
>
    送信する
</button>

参考: Zod | Error Handling in Zod

BLOG一覧へ