5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめに

Webサイトを作成する際にフォームが必要になること多いですよね。フロントエンドは比較的簡単に実装できますが、フォームのためだけにバックエンドを実装するのは面倒です。Google Formは便利ですが、そのままの見た目はちょっとデザイン的にアレですよね...
今回はReact Form Hook と Google Formを連携させてお手軽にフォームを作成する方法をご紹介します。ついでにValidationのためにZodも使用します。 ValidationはReact Form Hook単体でもできますが、Zodを使用することで、JSXがすっきりとした見た目になります。 加えて、TypeScriptを使用する際の型を提供してくれます。

前提

Google Formを作成した経験がある
React Form Hookを使用した経験がある

Zodを使いますが、前提知識は不要です

まずはGoogle Formを作成

今回は検証できれば良いので名前と年齢の2つにします
google fform.png

Reactプロジェクトを作成

筆者は普段、viteでReactを作成しています。

npm create vite@latest

TypeScriptは使用しないのでReact > JavaScriptのテンプレートを選択してください
App.cssindex.cssの内容を全て削除します。
index.cssに下記cssを追加してください

* {
  box-sizing: border-box;
}

p {
  padding: 0;
  margin: 0;
  margin-top: 10px;
}

button {
  margin-top: 10px;
}

必要なpackageをinstallします

npm install react-hook-form

Zodを使用するので下記2つもインストールしてください

npm install @hookform/resolvers 
npm install zod

App.jsxにコードを追加する

今回使用する全てのコードです。
FormURLのurlFormFieldNamesの内容は、後程各自が作成したGoogle Formから取得します

import "./App.css";
import { useForm } from "react-hook-form";

// Zod
import { zodResolver } from "@hookform/resolvers/zod";
import * as zod from "zod";

// Zodのschema定義 (validation)
const schema = zod.object({
  name: zod.string().min(1, { message: "必須項目です" }),
  age: zod.preprocess(
    (v) => Number(v),
    zod.number().min(2, { message: "年齢は2桁以上である必要があります" })
  ),
});

// Google FormのActon
const FormURL =
  "https://docs.google.com/forms/u/0/d/e/1FAIpQLSe6jUbmv6snQNZV-dm8zyqB-9-z7AIGZ2Y5wBAjTJCKW3hScw/formResponse";

// Google Formから取得したname属性の値
const FormFieldNames = {
  name: "entry.587594010",
  age: "entry.414111699",
};

function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    // zodを使用する設定
    resolver: zodResolver(schema),
  });

  const onSubmit = async (data) => {
    try {
      await fetch(FormURL, {
        method: "POST",
        // urlEncodeされた値をbodyに入れる
        body: provideUrlEncodedFormData(data),
        // 通常ではcorsに弾かれる (返却値のstatusは常に0 後述...)
        mode: "no-cors",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
      });

      // 成功後の処理を記述する (画面遷移とか)
    } catch (e) {
      alert("送信に失敗しました。ネットワーク状況を確認してください");
    }
  };

  return (
    <>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div className="fom-item">
          <label>
            <p>名前</p>
            <input type="text" {...register("name")} />
          </label>
          <p className="error-message">{errors.name?.message}</p>
        </div>
        <div className="form-item">
          <label>
            <p>年齢</p>
            <input type="text" {...register("age")} />
          </label>
          <p className="error-message">{errors.age?.message}</p>
        </div>
        <button type="submit">
          送信
        </button>
      </form>
    </>
  );
}

// request bodyに必要なurlEncodeされた値を提供
// originalのform情報を{entry.{number}: value}に変換する
function provideUrlEncodedFormData(originalFormData) {
  const result = {};
  Object.keys(originalFormData).map(
    (key) => (result[FormFieldNames[key]] = originalFormData[key])
  );
  return new URLSearchParams(result).toString();
}

export default App;

Google Formから必要な情報を取得する

App.jsxのうちFormURLのurl(action)FormFieldNamesのentryをGoogle Formから取得します。

Google Formの右上の目玉マーク(プレビュー)を押します
画面サイズが小さいと隠れてしまうので気をつけてください
スクリーンショット 2023-11-26 16.26.56.png

表示されたプレビュー画面で検証ツールを開き<formで検索するとactionを見つけることができます
スクリーンショット 2023-11-26 16.39.32.png

inputのentryはプレビューに仮データを入力することで簡単に見つけることが可能です
スクリーンショット 2023-11-26 16.38.35.png

年齢も同様に取得します
スクリーンショット 2023-11-26 16.36.51.png

entryの写し間違いを防ぐためにも、全てのinputで仮データを入れて取得するのが無難です

必要な情報を取得したらGoogle Form右下のフォームをクリアを押してください
スクリーンショット 2023-11-26 16.41.29.png


FormFieldNamesのkeyはregisterで定義したものにあわせてください

const FormFieldNames = {
  name: "entry.587594010",
  age: "entry.414111699",
};
<form onSubmit={handleSubmit(onSubmit)}>
  // ...省略
  <input type="text" {...register("name")} />
  // ...省略
  <input type="text" {...register("age")} />
  // ...省略
</form>

動作確認

Google Formの連携はこれで完了です。
動作確認の前にGoogle Formに閲覧制限がかかっていないか確認します。
Google FormプレビューのURLをクリップボードにコピーし、ブラウザのシークレットモードで閲覧可能か確認してください。
閲覧できない場合は、閲覧制限がかけられているので、リンクを知っている人全員がGoogle Formを閲覧できるよう変更してください。


npm run devでサーバーを起動してください。

なにも入力せずに送信ボタンを押した場合
スクリーンショット 2023-11-26 16.55.08.png

名前だけを入力した場合
スクリーンショット 2023-11-26 16.55.29.png

どちらもvalidationが機能しています。

名前と年齢を記入し送信ボタンを押します。
Google Formの回答を確認すると、データが登録されています
スクリーンショット 2023-11-26 16.57.34.png

ここまででReact Form Hookを利用したGoogle Form連携は完了です。

注意点

実装にあたっては下記の注意点を考慮した上で作成してください

fetch後に返されるstatusは常に0 (下記2点の場合ユーザー側は送信に失敗していることに気づけない)
1 google側のサーバーの問題 (滅多にない)
2 誤ってGoogle Formを削除した場合や必須項目をGoogle Formにのみ追加した場合 
(管理側が間違えなければ問題ない)


Wi-Fi(ネットワーク)非接続状態の検知はtry{}catch(){},async,awaitの使用で検知可能
カフェのFree Wi-Fiで1時間経過後通信が遮断されたタイミングで送信ボタンが押された場合など...

text以外の取り扱い

今回はtextのみ扱いましたが、checkboxやradioも同様に作成することが可能です。
下記記事はHTML, CSS, JavaScriptでGoogle Formと連携する方法です。
記事の中ではcheckbox, radio, select等も扱っています。参考にしてください。

終わりに

React Form HookもZodも触ったことがない状態で、土日休暇を利用して実装してみました。
Google Form連携を行わない場合でもReact Form Hookは有用なので、実装した甲斐がありました。
この記事が誰かのお役に立つと嬉しいです。

参考

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?