開発現場において、GitHub CopilotやChatGPTの利用が広まっています。テキストで指示するだけで、高精度な結果が得られますが、実際どこまでのことができるのでしょうか。

今回は、ChatGPTを使ってお問い合わせフォームを作成する流れを紹介します。手順を適切に踏まえれば、さらに複雑なWebアプリケーションでも開発できるはずです。

利用する技術について

今回は、以下の技術を利用します。

  • React
  • Ionic Framework
  • Firebase

前編となる今回はフロントエンドのみです。後半でFirebaseを利用したいと思います。

お問い合わせフォームの仕様

今回は、以下のようなお問い合わせフォームを作成します。

  • 入力フォームを表示する
  • 入力確認へ ボタンを押したら、入力内容を一覧して確認できる表示に切り替える
  • 送信 ボタンを押したら、入力内容をFirebaseに送信する
  • 送信完了したら、 送信完了 と表示する

また、フォームの入力項目は以下の通りです。入力項目はすべて必須です。

項目 項目種別 備考
お名前(姓名) テキスト
フリガナ テキスト
メールアドレス メールアドレス
問い合わせ種別 ドロップダウン
お問い合わせ内容 テキストエリア

ChatGPTを使う

今回はChatGPT 4oを利用しています。

たとえば「ReactとIonic Frameworkでお問い合わせフォームをデザインして」と依頼すると、項目は以下が選ばれました。

  • 名前
  • メールアドレス
  • 問い合わせ内容

これだけでは項目として不足しているので、初期の段階からきちんと指示してあげなければなりません。

ReactとIonic Frameworkを使って、お問い合わせフォームのデザインを行ってください。

入力項目は以下の通りです。

【フォーム入力項目】
- お名前(姓名)
  テキストフィールド
- フリガナ(セイメイ)
  テキストフィールド
- メールアドレス
  テキストフィールド(type=email)
- お問合せ内容
  テキストエリア
- お問合せ種別: 入力形式
  セレクトボックス
  - 選択肢:
    - 製品について
    - サービスについて
    - 料金について
    - その他

こうすると、以下のようなReactコンポーネントが生成されます。

import React, { useState } from "react";
import {
  IonPage,
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonItem,
  IonLabel,
  IonInput,
  IonTextarea,
  IonSelect,
  IonSelectOption,
  IonButton,
  IonList,
} from "@ionic/react";

const ContactForm: React.FC = () => {
  const [form, setForm] = useState({
    name: "",
    furigana: "",
    email: "",
    inquiryType: "",
    message: "",
  });

  const handleChange = (event: CustomEvent) => {
    const { name, value } = event.detail;
    setForm({ ...form, [name]: value });
  };

  const handleSubmit = () => {
    console.log("Form submitted:", form);
  };

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>お問い合わせフォーム</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent className="ion-padding">
        <IonList>
          <IonItem>
            <IonLabel position="stacked">お名前(姓名)</IonLabel>
            <IonInput name="name" value={form.name} onIonChange={handleChange} required></IonInput>
          </IonItem>
          <IonItem>
            <IonLabel position="stacked">フリガナ(セイメイ)</IonLabel>
            <IonInput name="furigana" value={form.furigana} onIonChange={handleChange} required></IonInput>
          </IonItem>
          <IonItem>
            <IonLabel position="stacked">メールアドレス</IonLabel>
            <IonInput type="email" name="email" value={form.email} onIonChange={handleChange} required></IonInput>
          </IonItem>
          <IonItem>
            <IonLabel position="stacked">お問い合わせ種別</IonLabel>
            <IonSelect name="inquiryType" value={form.inquiryType} onIonChange={handleChange} required>
              <IonSelectOption value="製品について">製品について</IonSelectOption>
              <IonSelectOption value="サービスについて">サービスについて</IonSelectOption>
              <IonSelectOption value="料金について">料金について</IonSelectOption>
              <IonSelectOption value="その他">その他</IonSelectOption>
            </IonSelect>
          </IonItem>
          <IonItem>
            <IonLabel position="stacked">お問い合わせ内容</IonLabel>
            <IonTextarea name="message" value={form.message} onIonChange={handleChange} required></IonTextarea>
          </IonItem>
        </IonList>
        <IonButton expand="full" onClick={handleSubmit}>送信</IonButton>
      </IonContent>
    </IonPage>
  );
};

export default ContactForm;

このフォームでは、以下の点が適切であると言えます。

  • メールアドレスが type="email" で指定されている
  • 全ての入力項目が required で指定されている
  • お問い合わせ種別が IonSelect で作られている

ただし、幾つかの点で気になるので、追加でプロンプトを使って指示します。

Ionic + Reactの開発をどうスタートすれば良いのか

このコンポーネントだけ出されても、すぐに使いこなすのは難しいでしょう。そこで、そもそもIonic + Reactの開発をどうスタートすれば良いのかを指示します。

ionic + reactでWebアプリケーションを開発する手順を教えてください

そうすると、以下のような結果が得られました。

1. 環境構築

1.1 Node.js のインストール

Ionic は Node.js を必要とするため、以下のコマンドでインストールを確認し、必要に応じてインストールしてください。

node -v  # バージョン確認
npm -v   # npm のバージョン確認

Node.js がインストールされていない場合は、公式サイト からダウンロードしてください。

1.2 Ionic CLI のインストール

Ionic CLI をグローバルインストールします。

npm install -g @ionic/cli

正しくインストールされたか確認:

ionic -v

2. プロジェクトの作成

以下のコマンドで新しい Ionic + React プロジェクトを作成します。

ionic start myApp blank --type=react

オプションの説明:

  • myApp: プロジェクト名
  • blank: 空のテンプレート
  • --type=react: React を使用する指定

作成されたプロジェクトのディレクトリへ移動:

cd myApp

3. アプリの起動

以下のコマンドで開発サーバーを起動します。

ionic serve

これでブラウザで http://localhost:8100/ にアクセスすると、アプリが表示されます。

4. UI コンポーネントの追加

Ionic の UI コンポーネントを使用して、お問い合わせフォームなどを作成できます。 src/pages/Home.tsx を編集して、フォームを作成します。

※ この下に先ほどのコンポーネントの内容が表示されるので、 Home.tsx の内容をまるっと入れ替えます。

ボタンのラベルの色を変更する

送信 というテキストが黒なので、見づらくなっています。そこで、白に変更してもらいます。

送信ボタンのテキストの色を白にしてください

そうすると、ボタンの部分だけ色が加えられます。

<IonButton expand="full" onClick={handleSubmit} style={{ color: "white" }}>送信</IonButton>

required属性が効いていない

現状は、ボタンのオンクリック属性を使っています。この場合、 required 属性が効かないので、フォームの送信前にバリデーションを行うように指示します。

ボタンをクリックした際に、required属性が効いていません。
全体をフォームで囲んで、onSubmitイベントで処理する形にしてください

こうすると、 <form>〜</form> が追加され、さらにボタンに対して type="submit" が追加されます。

<IonButton expand="full" type="submit" style={{ color: "white" }}>送信</IonButton>

入力確認にラベルを変更する

送信ボタンではなく、入力確認ボタンに変更してもらいます。

いきなり送信ではなく、確認画面を挟みたいと思います。ボタンのラベルは「送信」ではなく「入力確認へ」としてください。

こうすると、ボタンのラベルが変更されます。

<IonButton expand="full" type="submit" style={{ color: "white" }}>入力確認へ</IonButton>

入力確認画面を追加する

今回は入力画面の後、確認画面に遷移する流れを入れます。そこで、現在のフォームの内容を別コンポーネントに分けて、入力・確認・送信完了という状態に応じて表示を分けられるように指示します。

お問い合わせフォーム全体に対して、状態を管理できるようにしてください

- 入力(input)
- 確認(confirm)
- 送信完了(sent)

の3つの状態があり、最初はinputです。入力確認へを押した際に、confirmに変更します。

confirmでは、入力内容を確認します。一番下に「送信する」ボタンを配置します。

現状のフォームの内容を InputForm コンポーネントにしてください。状態によって、入力・確認・送信完了を切り替えられるようにしてください。

このような指示を行うことで、 InputForm というコンポーネントにフォームの内容が切り出されます。また、それらの状態を step という変数で管理するようになります。

ただし、入力と送信完了は ContactForm に残っていたので、さらに切り出しを指示します。

step = confirm、step=sentの場合も、InputFormと同様にコンポーネントを分けてください。

これで InputFormConfirmFormSentMessage の切り替えが可能になります。この時の注意点として、ChatGPTでは1つのファイルしか扱えないため、コンポーネントを別ファイルとして切り分けられません。これはコードがすべてできあがった後、自分で切り分ける必要があります。

const ConfirmForm = ({ form, handleSend }) => (
  <>
    <IonList>
      <IonItem><IonLabel>お名前</IonLabel><p>{form.name}</p></IonItem>
      <IonItem><IonLabel>フリガナ</IonLabel><p>{form.furigana}</p></IonItem>
      <IonItem><IonLabel>メールアドレス</IonLabel><p>{form.email}</p></IonItem>
      <IonItem><IonLabel>お問い合わせ種別</IonLabel><p>{form.inquiryType}</p></IonItem>
      <IonItem><IonLabel>お問い合わせ内容</IonLabel><p>{form.message}</p></IonItem>
    </IonList>
    <IonButton expand="full" onClick={handleSend} style={{ color: "white" }}>送信する</IonButton>
  </>
);

const SentMessage = () => <p>送信が完了しました。</p>;

const ContactForm: React.FC = () => {
  const [form, setForm] = useState<>({
    name: "",
    furigana: "",
    email: "",
    inquiryType: "",
    message: "",
  });
  const [step, setStep] = useState<"input" | "confirm" | "sent">("input");

  const handleChange = (
    event: CustomEvent<InputChangeEventDetail | SelectChangeEventDetail | TextareaChangeEventDetail>
  ) => {
    const target = event.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
    setForm({ ...form, [target.name]: target.value });
  };

  const handleConfirm = (event: React.FormEvent) => {
    event.preventDefault();
    if (!form.name || !form.furigana || !form.email || !form.inquiryType || !form.message) {
      alert("すべての項目を入力してください。");
      return;
    }
    setStep("confirm");
  };

  const handleSend = () => {
    console.log("送信されました:", form);
    setStep("sent");
  };

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>お問い合わせフォーム</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent className="ion-padding">
        {step === "input" && <InputForm form={form} handleChange={handleChange} handleConfirm={handleConfirm} />}
        {step === "confirm" && <ConfirmForm form={form} handleSend={handleSend} />}
        {step === "sent" && <SentMessage />}
      </IonContent>
    </IonPage>
  );
};

細かな修正

さらに細かく修正指示を行います。

確認画面から、入力画面に戻れるようにしてください。入力内容は保持してください。

これで ConfirmFormに、InputFormに戻る処理 handleEdit (step = inputにする)が追加されます。

<IonButton expand="full" onClick={handleEdit} style={{ color: "white", marginBottom: "10px" }}>戻る</IonButton>

const handleEdit = () => {
  setStep("input");
};
お問い合わせ内容で改行を使うと、確認画面のデザインが崩れます。調整してください。

この指示で、確認画面のお問い合わせ内容のところに { whiteSpace: "pre-line" } が追加されます。

<p style={{ whiteSpace: "pre-line" }}>{form.message}</p>

TypeScriptの型を適用する

form などに any が使われているので、型を適用してもらいます。

TypeScriptで使えるように、型を適切に定義してください。anyは極力なくしてください。

こうすると、以下のようになります(変更された主な部分のみ)。これで型安全に開発が行えます。

interface FormState {
  name: string;
  furigana: string;
  email: string;
  inquiryType: string;
  message: string;
}

interface InputFormProps {
  form: FormState;
  handleChange: (event: CustomEvent<InputChangeEventDetail | SelectChangeEventDetail | TextareaChangeEventDetail>) => void;
  handleConfirm: (event: React.FormEvent) => void;
}

const InputForm: React.FC<InputFormProps> = ({ form, handleChange, handleConfirm }) => (
  // 略
);

interface ConfirmFormProps {
  form: FormState;
  handleSend: () => void;
  handleEdit: () => void;
}

const ConfirmForm: React.FC<ConfirmFormProps> = ({ form, handleSend, handleEdit }) => (
  // 略
);

const ContactForm: React.FC = () => {
  const [form, setForm] = useState<FormState>({
    name: "",
    furigana: "",
    email: "",
    inquiryType: "",
    message: "",
  });
  const [step, setStep] = useState<"input" | "confirm" | "sent">("input");

  const handleChange = (
    event: CustomEvent<InputChangeEventDetail | SelectChangeEventDetail | TextareaChangeEventDetail>
  ) => {
    const target = event.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
    setForm({ ...form, [target.name]: target.value });
  };

  const handleConfirm = (event: React.FormEvent) => {
    // 略
  };
  // 略
};

最終版

最終的に、JavaScript初学者でも分かりやすいようにコメントを追加してもらいます。

今回のコードに対して、JavaScript初学者でも分かりやすいようにコメントを記述してください。

そして生成されたのが、以下のコードになります。

import React, { useState } from "react";
import {
  IonPage,
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonItem,
  IonLabel,
  IonInput,
  IonTextarea,
  IonSelect,
  IonSelectOption,
  IonButton,
  IonList,
  InputChangeEventDetail,
  SelectChangeEventDetail,
  TextareaChangeEventDetail,
} from "@ionic/react";

// フォームの状態を管理するインターフェース
interface FormState {
  name: string;
  furigana: string;
  email: string;
  inquiryType: string;
  message: string;
}

// 入力フォームのプロパティを定義
interface InputFormProps {
  form: FormState;
  handleChange: (event: CustomEvent<InputChangeEventDetail | SelectChangeEventDetail | TextareaChangeEventDetail>) => void;
  handleConfirm: (event: React.FormEvent) => void;
}

// 入力フォームコンポーネント
const InputForm: React.FC<InputFormProps> = ({ form, handleChange, handleConfirm }) => (
  <form onSubmit={handleConfirm}>
    <IonList>
      <IonItem>
        <IonLabel position="stacked">お名前(姓名)</IonLabel>
        <IonInput name="name" value={form.name} onIonInput={handleChange} required></IonInput>
      </IonItem>
      <IonItem>
        <IonLabel position="stacked">フリガナ(セイメイ)</IonLabel>
        <IonInput name="furigana" value={form.furigana} onIonInput={handleChange} required></IonInput>
      </IonItem>
      <IonItem>
        <IonLabel position="stacked">メールアドレス</IonLabel>
        <IonInput type="email" name="email" value={form.email} onIonInput={handleChange} required></IonInput>
      </IonItem>
      <IonItem>
        <IonLabel position="stacked">お問い合わせ種別</IonLabel>
        <IonSelect name="inquiryType" value={form.inquiryType} onIonChange={handleChange} required>
          <IonSelectOption value="製品について">製品について</IonSelectOption>
          <IonSelectOption value="サービスについて">サービスについて</IonSelectOption>
          <IonSelectOption value="料金について">料金について</IonSelectOption>
          <IonSelectOption value="その他">その他</IonSelectOption>
        </IonSelect>
      </IonItem>
      <IonItem>
        <IonLabel position="stacked">お問い合わせ内容</IonLabel>
        <IonTextarea name="message" value={form.message} onIonInput={handleChange} required></IonTextarea>
      </IonItem>
    </IonList>
    <IonButton expand="full" type="submit" style={{ color: "white" }}>入力確認へ</IonButton>
  </form>
);

// 確認画面のプロパティを定義
interface ConfirmFormProps {
  form: FormState;
  handleSend: () => void;
  handleEdit: () => void;
}

// 確認画面コンポーネント
const ConfirmForm: React.FC<ConfirmFormProps> = ({ form, handleSend, handleEdit }) => (
  <>
    <IonList>
      <IonItem><IonLabel>お名前</IonLabel><p>{form.name}</p></IonItem>
      <IonItem><IonLabel>フリガナ</IonLabel><p>{form.furigana}</p></IonItem>
      <IonItem><IonLabel>メールアドレス</IonLabel><p>{form.email}</p></IonItem>
      <IonItem><IonLabel>お問い合わせ種別</IonLabel><p>{form.inquiryType}</p></IonItem>
      <IonItem><IonLabel>お問い合わせ内容</IonLabel><p style={{ whiteSpace: "pre-line" }}>{form.message}</p></IonItem>
    </IonList>
    <IonButton expand="full" onClick={handleEdit} style={{ color: "white", marginBottom: "10px" }}>戻る</IonButton>
    <IonButton expand="full" onClick={handleSend} style={{ color: "white" }}>送信する</IonButton>
  </>
);

// 送信完了メッセージコンポーネント
const SentMessage: React.FC = () => <p>送信が完了しました。</p>;

// メインの問い合わせフォームコンポーネント
const ContactForm: React.FC = () => {
  const [form, setForm] = useState<FormState>({
    name: "",
    furigana: "",
    email: "",
    inquiryType: "",
    message: "",
  });
  const [step, setStep] = useState<"input" | "confirm" | "sent">("input");

  // 入力値を更新する関数
  const handleChange = (
    event: CustomEvent<InputChangeEventDetail | SelectChangeEventDetail | TextareaChangeEventDetail>
  ) => {
    const target = event.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
    setForm({ ...form, [target.name]: target.value });
  };

  // 入力内容を確認画面に遷移する処理
  const handleConfirm = (event: React.FormEvent) => {
    event.preventDefault();
    if (!form.name || !form.furigana || !form.email || !form.inquiryType || !form.message) {
      alert("すべての項目を入力してください。");
      return;
    }
    setStep("confirm");
  };

  // 入力内容を送信し、送信完了画面に遷移する処理
  const handleSend = () => {
    console.log("送信されました:", form);
    setStep("sent");
  };

  // 確認画面から入力画面に戻る処理
  const handleEdit = () => {
    setStep("input");
  };

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>お問い合わせフォーム</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent className="ion-padding">
        {step === "input" && <InputForm form={form} handleChange={handleChange} handleConfirm={handleConfirm} />}
        {step === "confirm" && <ConfirmForm form={form} handleSend={handleSend} handleEdit={handleEdit} />}
        {step === "sent" && <SentMessage />}
      </IonContent>
    </IonPage>
  );
};

export default ContactForm;

これでデザイン面は完成となります。

利用時の注意点

ChatGPTに限りませんが、AIコーディングアシスタントを利用する場合には、以下の点に注意が必要です。

細かく定義して依頼する

スクリーンショットをアップロードして、これをデザインしてといった大雑把な内容で依頼すると、結果も大雑把なものになりがちです。画面の項目や切り分け方など、細かく指示すると結果の精度も高くなります。

複数の指示を一度にしない

修正内容が複数あるとしても、指示は一度に一つとしておく方が結果の精度は高いです。複数の指示を行うと、中途半端な修正になったり、取りこぼしが発生します。

修正内容が自分で判断できるようにする

指示する側が分かっていない技術を用いると、その結果が正しいのか間違っているのか判断できません。ChatGPTが出力する内容が間違っている場合も多々あり、その際にどう修正を行えば良いかを指示できなければいけません。

バグ修正ボタンもあるのですが、それも正しく動作する保証はありません。不備を適切に指摘できないと、別な不具合が発生するといった事態になりかねないので注意してください。

まとめ

今回はChatGPTを利用して、お問い合わせフォームをデザインする流れを解説しました。細かく指示を行うこと、画面の項目などを厳密に定義することで精度の高い結果が得られます。人のようによしなに解釈してくれる訳ではない(それでもかなり柔軟ではあるのですが)ので、指示をしっかり行うことが重要です。

ChatGPTをコーディングアシスタントにすることで、これまでよりも開発生産性が大幅に向上するはずです。上手に使いこなしてください。特に初心者の方は、段階を追って少しずつ構築していくことで、ChatGPTの活用に対する理解も深まります。ぜひ、最初はシンプルな構成から始めてみてください。