【Amplify Gen2】リアルタイムサブスクリプションで発生する"Cannot read properties of null (reading 'id')"のエラー対応方法
はじめに
コンサル部の神野です。
Amplify関連の検証を進めていた際に、画面側でリアルタイムサブスクリプションがうまくいかず、下記エラーが発生したことがありました。
Uncaught TypeError: Cannot read properties of null (reading 'id')
一般的なエラーメッセージであまり情報もなく、何が原因なんだろうと思い調べて検証していたところ解決したので、共有させていただきます。
前提
今回もリアルタイムサブスクリプション実装の記事をベースに話を進めていきます。
記事やレポジトリのリンクを下記に記載しているので必要に応じてご参照ください。
リアルタイムサブスクリプション実装の記事
レポジトリ
まず原因
原因として自分で作成したMutationが適切ではなかったというだけでした。
Amplify側で設定されるid
、createdAt
、updatedAt
も返却するようMutationに含める必要があります。
自分が実行したMutation
mutation CreateDeviceStatus($input: CreateDeviceStatusInput!) {
createDeviceStatus(input: $input) {
id
device_Id
humidity
temperature
voltage
last_updated
status_code
status_description
status_state
}
}
正しいMutation
mutation CreateDeviceStatus($input: CreateDeviceStatusInput!) {
createDeviceStatus(input: $input) {
id
device_Id
humidity
temperature
voltage
last_updated
status_code
status_description
status_state
+ createdAt
+ updatedAt
}
}
差分としてはAmplify側で自動設定されるcreatedAt
、updatedAt
を含めているかどうかです。
ただ、どちらのMutationもエラーなく実行されるのにどうして画面側でエラーが出てしまうのかと気になり深掘りしていきたいと思います。
調査
Amplifyで設定するデータの定義は下記の通りとします。
データ定義
const schema = a
.schema({
DeviceStatus: a
.model({
device_Id: a.string(),
status_code: a.string(),
status_state: a.string(),
status_description: a.string(),
temperature: a.float(),
humidity: a.float(),
voltage: a.string(),
last_updated: a.string(),
})
})
.authorization((allow) => [allow.authenticated()]);
Mutation実行
定義に従ってid
、createdAt
、updatedAt
などは一旦気にせず、下記Mutationをコンソール上から実行してみます。
mutation MyMutation {
createDeviceStatus(input: {device_Id: "device_003", humidity: 45.7, last_updated: "2024-01-20T15:30:22Z", status_code: "200", status_description: "Normal operation", status_state: "ACTIVE", temperature: 23.4, voltage: "12.3"}) {
device_Id
humidity
last_updated
status_code
status_description
status_state
temperature
voltage
}
}
実行結果
エラーなく実行され、登録されたデータが返却されました。
{
"data": {
"createDeviceStatus": {
"device_Id": "device_003",
"humidity": 45.7,
"last_updated": "2024-01-20T15:30:22Z",
"status_code": "200",
"status_description": "Normal operation",
"status_state": "ACTIVE",
"temperature": 23.4,
"voltage": "12.3"
}
}
}
DynamoDBにもデータが登録されているか確認してみます。
{
"id": {
"S": "89de53de-2536-401a-8073-c5012e40c212"
},
"createdAt": {
"S": "2024-12-24T12:54:43.364Z"
},
"device_Id": {
"S": "device_003"
},
"humidity": {
"N": "45.7"
},
"last_updated": {
"S": "2024-01-20T15:30:22Z"
},
"status_code": {
"S": "200"
},
"status_description": {
"S": "Normal operation"
},
"status_state": {
"S": "ACTIVE"
},
"temperature": {
"N": "23.4"
},
"updatedAt": {
"S": "2024-12-24T12:54:43.364Z"
},
"voltage": {
"S": "12.3"
},
"__typename": {
"S": "DeviceStatus"
}
}
id
もcraetedAt
もupdatedAt
も登録されていますね。
AppSync のリゾルバーで実行される VTL(Velocity Template Language)スクリプトによって処理され、DynamoDB に保存される前に自動的に付与されます。
補足 VTLスクリプト
## [Start] Initialization default values. **
$util.qr($ctx.stash.put("defaultValues", $util.defaultIfNull($ctx.stash.defaultValues, {})))
$util.qr($ctx.stash.defaultValues.put("id", $util.autoId()))
#set( $createdAt = $util.time.nowISO8601() )
$util.qr($ctx.stash.defaultValues.put("createdAt", $createdAt))
$util.qr($ctx.stash.defaultValues.put("updatedAt", $createdAt))
$util.toJson({
"version": "2018-05-29",
"payload": {}
})
## [End] Initialization default values. **
画面側のエラーログ
一方で画面側を見てみると下記エラーが発生して画面が更新されていませんでした。
Amplify内部のエラーが発生していて、リアルタイムサブスクリプションの実装箇所まで到達していないような動きをしていました。
同じような原因に直面している方はいないかと調べていたところ下記Issueで議論がされていました。
これをみると、同じようなエラーだがちょっと違う事象なのかなーと思っていたところ下記一文が気になりました。
The errors were essentially that
createdAt
,updatedAt
,owner
can't be null.
その方の場合は、自作のMutationのクエリにcreatedAt
、updatedAt
、owner
も返却する必要があったそうです(id
については既に追加済みでした)。
この情報を参考に、私も検証を進めてみました。まず全てのカラム(id
、createdAt
、updatedAt
も)を返却するMutationを実行し、その後、一つずつカラムを除外していく形で検証を行いました。
その結果、以下の3つのカラムが1つでも返却しない場合にのみ、タイトルのエラーが発生し、Amplify側内部処理で使用する必要なカラムということがわかりました。
id
createdAt
updatedAt
自分で定義したカラム(device_Id
、temperature
など)については、Mutationで返却するよう含めなくてもエラーは発生せず、フロント側のリアルタイムサブスクリプションは正常に動作しました。
補足:ownerについて
owner
については今回の設定では、データの所有者のみアクセスではなく認証されたユーザー全員アクセス可としているので、カラムとして存在しない状態でした。下記設定のようにデータの所有者のみアクセスとすると、別途カラムにデータが登録されてデータを返却するよう考慮する必要があるかと思います。
const schema = a
.schema({
DeviceStatus: a
.model({
device_Id: a.string(),
status_code: a.string(),
status_state: a.string(),
status_description: a.string(),
temperature: a.float(),
humidity: a.float(),
voltage: a.string(),
last_updated: a.string(),
})
})
- .authorization((allow) => [allow.authenticated()]);
+ .authorization((allow) => [allow.owner()]);
まとめ
今までの結論をまとめると下記の通りとなります。
- エラーの原因は、Amplifyが自動生成する必須フィールドが自作のMutationで返却しない作りであった
id
、createdAt
、updatedAt
をMutationに返却するよう追加しエラーは解消- これらのフィールドはAmplifyの内部処理に必要で存在しない場合はサブスクリプションに失敗する
おわりに
簡単ではありますが、一般的なエラーメッセージで情報が少なかったため、本記事を共有させていただきました。
少しでも本記事が参考になりましたら幸いです!
最後までご覧いただきありがとうございました!!