📮

【Astro ✖️ React Hook Form】確認画面付きのフォームを作る

2024/08/12に公開

はじめに

Astro で確認画面付きのフォームを作る方法です。
バリデーションは React Hook Form と Zod、確認画面へのデータの受け渡しは sessionStrage を使用します。

完成物

https://astro-form-ten.vercel.app/
※ 送信機能は実装していません。

ファイル構成

|- components
| |- Form.tsx
| |- Confirm.tsx
|
|- pages
| |- index.astro
| |- confirm.astro
| |- thanks.astro

ライブラリのインストール

Astro で React を使用するため、@astrojs/react をインストールします。

npx astro add react

バリデーション用の React Hook Form と Zod をインストールします。

npm install react-hook-form zod @hookform/resolvers

入力画面

components/Form.tsx
components/Form.tsx
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as zod from "zod";

// バリデーション設定
const schema = zod.object({
  name: zod.string().min(1, { message: "必須項目です" }),
  email: zod.string().email({ message: "有効なメールアドレスを入力してください" })
});

// フォームの入力内容の型
type FormData = {
  name: string;
  email: string;
};

const Form = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data: FormData) => {
    //送信するデータをsessionStorageに保存
    sessionStorage.setItem('contactData', JSON.stringify(data));
    window.location.href = '/confirm';
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label htmlFor="name">
          名前
        </label>
        <input
          type="text"
          id="name"
          {...register("name")}
          placeholder="山田 太郎" />
        {errors.name && <p>{errors.name.message}</p>}
      </div>
      <div>
        <label htmlFor="email">
          メールアドレス
        </label>
        <input
          type="email"
          id="email"
          {...register("email")}
          placeholder="sampla@abc.com" />
        {errors.email && <p>{errors.email.message}</p>}
      </div>
      <button type="submit">
        確認画面へ
      </button>
    </form>
  )
}

export default Form;

バリデーションは、React Hook Form と Zod を使用しています。

pages/index.astro
pages/index.astro
---
import Layout from "../layouts/Layout.astro";
import FormComponent from "../components/Form.tsx";
---

<Layout>
  <FormComponent client:load />
</Layout>

.astro ファイルで、React コンポーネントを表示します。
コンポーネントの呼び出しにはclient:loadを追加します。

確認画面にデータを受け渡す

const onSubmit = (data: FormData) => {
  //送信するデータをsessionStorageに保存
  sessionStorage.setItem("contactData", JSON.stringify(data));
  window.location.href = "/confirm";
};

確認画面へ遷移するボタンをクリックしたら、sessionStorage に送信するデータを保存して/confirmに遷移します。

確認画面

components/Confirm.tsx
components/Confirm.tsx
import { useState, useEffect } from 'react';
import { useForm } from "react-hook-form";

// フォームの入力内容の型定義
type FormData = {
  name: string;
  email: string;
};

const Confirm = () => {
  const { register, handleSubmit } = useForm<FormData>({});
  const [contactData, setContactData] = useState<Partial<FormData>>({});

  useEffect(() => {
    // sessionStorageからデータを取得
    const sessionData = sessionStorage.getItem('contactData');
    if (!sessionData) {
      // データがない場合はフォームに戻る
      window.location.href = '/';
    } else {
      setContactData(JSON.parse(sessionData));
    }
  }, []);

  const onSubmit = async (data: FormData) => {
    try {
      // 送信処理を記述する

      // 送信後の処理
      // sessionStorageのデータを削除して/thanksに遷移
      sessionStorage.removeItem('contactData');
      window.location.href = '/thanks';
    } catch (e) {
      alert("送信に失敗しました。ネットワーク状況を確認してください");
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <p>名前</p>
          <p>{contactData.name}</p>
          <input
            type="hidden"
            id="name"
            {...register("name")}
            value={contactData.name}
          />
        </div>
        <div>
          <p>メールアドレス</p>
          <p>{contactData.email}</p>
          <input
            type="hidden"
            id="email"
            {...register("email")}
            value={contactData.email}
          />
        </div>
      <button
        type="submit"
      >
        送信する
      </button>
      <button
        type="button"
        onClick={() => history.back()}
      >
        入力画面に戻る
      </button>
    </form>
  );
};

export default Confirm;
pages/confirm.astro
pages/confirm.astro
---
import Layout from "../layouts/Layout.astro";
import ConfirmComponent from "../components/Confirm.tsx";
---

<Layout>
  <ConfirmComponent client:load />
</Layout>

.astro ファイルで、React コンポーネントを表示します。
コンポーネントの呼び出しにはclient:loadを追加します。

sessionStrage からデータを取得する

const [contactData, setContactData] = useState<Partial<FormData>>({});

useEffect(() => {
  // sessionStorageからデータを取得
  const sessionData = sessionStorage.getItem("contactData");
  if (!sessionData) {
    // データがない場合はフォームに戻る
    window.location.href = "/";
  } else {
    setContactData(JSON.parse(sessionData));
  }
}, []);

setContactData(JSON.parse(sessionData));で sessionStrage に保存したデータを json 形式で取得します。

また、/confirm に直接アクセスされた時に sessionStorage にデータがなければ入力画面に遷移させます。

送信内容を表示する

<div>
  <p>名前</p>
  <p>{contactData.name}</p>
  <input
    type="hidden"
    id="name"
    {...register("name")}
    value={contactData.name}
  />
</div>
<div>
  <p>メールアドレス</p>
  <p>{contactData.email}</p>
  <input
    type="hidden"
    id="email"
    {...register("email")}
    value={contactData.email}
  />
</div>

{contactData.name}のように各データを表示出来ます。
送信用にtype="hidden"の input タグのvalueにも各データを格納します。

送信する

const onSubmit = async (data: FormData) => {
  try {
    // 送信処理を記述する

    // 送信後の処理
    // sessionStorageのデータを削除して/thanksに遷移
    sessionStorage.removeItem("contactData");
    window.location.href = "/thanks";
  } catch (e) {
    alert("送信に失敗しました。ネットワーク状況を確認してください");
  }
};

送信処理が完了後に、sessionStorage の内容を削除して、/thanks に遷移させます。

終わりに

Astro に React を導入してフォームを作成しました。
React の導入もコマンドひとつで簡単に出来ますし、React Hook Form を使用すればフォーム周りの機能も簡単に実装可能です。

今回は実装していませんが、フォームの送信機能は Google Form と GAS を使用することで全て無料で作成も可能です。

Discussion