「Cannot read properties of undefined (reading 'response')」で悩んだ話 - Google FormsとSpreadsheetで作るGASの違い

# はじめに

みなさん、こんにちは！これまで見るためだけにQitaを使用していましたが、社会人になったということで投稿をしていこうと思います。わかりずらい部分があるかもしれないですが大目に見てください💦
今日は私が実際にハマった「Google Apps Script（GAS）」のトラブルについてお話しします。
フォーム送信時のデータ処理でこのようなエラーが出力されました。

```
Cannot read properties of undefined (reading 'response')
```

あれ？ネットの記事を見てるとこれで取得できてるんだけどなぁ...
色々調べていくと、**GASをどこから作るか**（Google FormsかGoogle Spreadsheetか）で受け取れるデータの構造が全然違うことがわかりました。

# 発端：「Cannot read properties of undefined (reading 'response')」エラー発生

最初にやろうとしたのは単純なこと。Googleフォームの回答があったら自動返信メールを送る機能です。ネットで見つけたサンプルコードを参考に書いてみました。

```javascript
function onSubmit(e) {
  // 回答者のメールアドレスを取得
  var email = e.response.getRespondentEmail();
  
  // 各回答内容を取得
  var itemResponses = e.response.getItemResponses();
  var message = "以下の内容で回答を受け付けました。\n\n";
  
  for (var i = 0; i < itemResponses.length; i++) {
    var itemResponse = itemResponses[i];
    message += itemResponse.getItem().getTitle() + ": " + itemResponse.getResponse() + "\n";
  }
  
  // メール送信
  MailApp.sendEmail(email, "回答ありがとうございました", message);
}
```

実行すると...

```
Cannot read properties of undefined (reading 'response')
```

「え？なんで？」と思って調べまくりました。コードは間違ってないはず...。

# 原因：SpreadsheetからGASプロジェクトを作成していた

数時間悩んだ末に気づいたのは、**GASプロジェクトの作成場所**が問題だったんです！

私はフォームを作成した後、「回答」タブから「Googleスプレッドシートで表示」をクリックして、そのスプレッドシートから「拡張機能 > Apps Script」でGASエディタを開いていました。

これが落とし穴でした！**SpreadsheetからGASを作ると、受け取るイベントオブジェクトの構造が全く違う**んです。`e.response`というプロパティが存在せず、代わりに`e.values`や`e.namedValues`を使う必要があったんです。

# 決定的な違い：フォームから作るGASとスプレッドシートから作るGASの差

## 1. Google Forms側から作成したGAS

Formsから直接「スクリプトエディタ」を開くと（フォームの編集画面で右上の「︙」から「スクリプトエディタ」を選択）、このようなイベントオブジェクトが使えます：

```javascript
function onSubmit(e) {
  // e.responseが使える！
  var response = e.response;
  
  // 回答者情報
  var email = response.getRespondentEmail();
  var timestamp = response.getTimestamp();
  
  // 質問と回答のセット
  var itemResponses = response.getItemResponses();
  for (var i = 0; i < itemResponses.length; i++) {
    var question = itemResponses[i].getItem().getTitle();
    var answer = itemResponses[i].getResponse();
    Logger.log(question + ": " + answer);
  }
}
```

## 2. Spreadsheet側から作成したGAS

一方、スプレッドシートから作った場合はこうなります：

```javascript
function onSubmit(e) {
  // e.responseは存在しない！
  // 代わりにe.valuesとe.namedValuesが使える
  
  // 値の配列（スプレッドシートの行と同じ順序）
  var values = e.values;
  Logger.log("タイムスタンプ: " + values[0]);
  Logger.log("1番目の回答: " + values[1]);
  
  // 質問名をキーとした連想配列
  var namedValues = e.namedValues;
  // 各値は配列になっているので[0]が必要
  Logger.log("名前: " + namedValues["お名前"][0]);
  
  // メールアドレスはvaluesやnamedValuesから取得する必要がある
  // フォームでメールアドレスを収集している場合
  var email = namedValues["メールアドレス"][0];
}
```

# 他にもいろいろ違いがあるんです！

単にプロパティ名が違うだけじゃなく、他にも大きな違いがあります：

## 処理タイミングの違い
- Forms側：フォーム送信直後に処理される
- Spreadsheet側：スプレッドシートに記録された後に処理される（少し遅延あり）

## 取得できるデータの詳細度
- Forms側：質問タイプ、選択肢情報など詳細情報も取得可能
- Spreadsheet側：基本的に回答値のみ取得可能

## データ操作のしやすさ
- Forms側：フォームに関する操作が容易
- Spreadsheet側：スプレッドシートの機能（並べ替え、フィルタなど）と連携しやすい

## トリガーのタイミング
- Forms側：フォーム送信時のみ
- Spreadsheet側：スプレッドシートの編集時にも発火する可能性がある

## エラーハンドリングの違い
- Forms側：エラー発生時にユーザーに通知されにくい
- Spreadsheet側：スプレッドシートに履歴が残るため追跡しやすい

## パフォーマンス
- Forms側：処理が直接実行されるため若干早い
- Spreadsheet側：スプレッドシートへの書き込み後に処理されるため若干遅い

# 私の解決策：目的に合わせて選ぶ

結局、私はこの違いを理解した上で**フォーム側からGASを作り直し**ました。自動返信メールを送るには、回答者のメールアドレスを簡単に取得できるForms側が適していたからです。

ただ、データの集計や分析が目的なら、Spreadsheet側の方が便利なケースも多いです。

# 実際のコード例

## Forms側（自動返信メールの例）
```javascript
function onSubmit(e) {
  var email = e.response.getRespondentEmail();
  var name = "";
  
  // 名前を取得
  var itemResponses = e.response.getItemResponses();
  for (var i = 0; i < itemResponses.length; i++) {
    if (itemResponses[i].getItem().getTitle() === "お名前") {
      name = itemResponses[i].getResponse();
      break;
    }
  }
  
  // メール送信
  MailApp.sendEmail(
    email,
    "お問い合わせありがとうございます",
    name + "様\n\nお問い合わせを受け付けました。担当者から連絡します。"
  );
}
```

## Spreadsheet側（回答集計の例）
```javascript
function onSubmit(e) {
  var values = e.values;
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("集計");
  
  // 性別ごとの集計
  var gender = e.namedValues["性別"][0];
  var currentCount = 0;
  
  if (gender === "男性") {
    currentCount = sheet.getRange("B2").getValue();
    sheet.getRange("B2").setValue(currentCount + 1);
  } else if (gender === "女性") {
    currentCount = sheet.getRange("B3").getValue();
    sheet.getRange("B3").setValue(currentCount + 1);
  }
}
```

# デバッグのコツ

このエラーに遭遇したら、まずは**イベントオブジェクトの構造**を確認するのがオススメです！

```javascript
function onFormSubmit(e) {
  // デバッグ用：イベントオブジェクトの構造を調べる
  Logger.log("イベントオブジェクトのプロパティ一覧:");
  for (var prop in e) {
    Logger.log(" - " + prop);
  }
  
  // 値を確認
  if (e.response) {
    Logger.log("Forms側から実行されています");
  } else if (e.values) {
    Logger.log("Spreadsheet側から実行されています");
    Logger.log("値の一覧: " + e.values);
  }
}
```

このコードを実行すれば、どちらのタイプのイベントオブジェクトを受け取っているかすぐに分かります！

# まとめ

「Cannot read properties of undefined (reading 'response')」というエラーに遭遇したら、それはSpreadsheet側からGASを実行しているのに、Forms側のデータ構造を前提としたコードを書いている可能性が高いです。

GASでフォーム送信を処理する際は、**プロジェクトを作る場所**がすごく重要です！

- **回答者へのリアルタイム対応**が必要なら → **Forms側から作成**
- **データの集計・分析**が主目的なら → **Spreadsheet側から作成**

みなさんも私のように「Cannot read properties of undefined (reading 'response')」と悩まないよう、この違いをぜひ覚えておいてください。使い分けることで、GASの可能性がグッと広がりますよ！

何か質問や共有したい経験があれば、コメント欄でお待ちしています！

---

この記事が役に立ったらいいねやコメントをいただけると嬉しいです！😊