React+TypeScriptでアプリを開発している時のこと。
「ユーザーがformに入力した内容をリアルタイムに画面にに反映したいな」と思い(チャットの様なものだと思ってください)、調べているとget()
ではなく、onSnapshot()
を使えばFirestore(db)に保存されたデータを即時画面に反映できると知り、使ってみた結果即時反映させることには成功したものの、なぜかserverTimestamp()がnullになる
エラーに直面しました。
その時の対処法の解説になります。
コードの例
以下は上手く行った時のReactのコード例ですが、他のフレームワークやライブラリでも同じ様な感じだと思います。
useEffect(() => {
let comments: any = [];
const unsubscribe: any = db
.collection("posts")
.doc(id)
.collection("comments")
.orderBy("createdAt", "desc")
.onSnapshot((snapshots) => {
snapshots.docChanges().forEach((change) => {
const data = change.doc.data({ serverTimestamps: "estimate" });
const changeType = change.type;
const date = data.createdAt.toDate();
switch (changeType) {
case "added":
comments.push({ ...data, createdAt: date });
break;
case "modified":
const index = comments.findIndex(
(comment: any) => comment.id === change.doc.id
);
comments[index] = comment;
break;
case "removed":
comments = comments.filter(
(comment: any) => comment.id !== change.doc.id
);
break;
default:
break;
}
});
setComment(comments);
unsubscribe();
});
}, []);
解決した方法
解決した方法はかなり簡単でした。。。
doc.data({ serverTimestamps: "estimate" }).createdAt
解説
FirebaseでgetやonSnapshotを使ってデータを呼び出した際に、
snapshots.docs.forEach(doc => {...})
の様な感じで、データを取り出すと思います。
この後に、doc.data()
とすると一つ一つのデータのオブジェクトが取得できます。
しかし、
doc.data().createdAt
とすると、最新(追加した瞬間)のデータがnullになってしまうのですが、
doc.data({ serverTimestamps: "estimate" }).createdAt
とすることで、追加した瞬間のnullの状態のtimestampに対して、timestampを推定して、確定するまでとりあえず仮のTimestamp
を入れておいてくれます。
Firebaseの公式ドキュメントを見ていただけたらわかると思いますが、このdoc.data()
のdata()
に関して以下の様な解説がされています。
ドキュメント内のすべてのフィールドをオブジェクトとして取得します。文書が存在しない場合は 'undefined' を返します。デフォルトでは、まだ最終的な値に設定されていない FieldValue.serverTimestamp() の値は null として返されます。これをオーバーライドするには、オプションオブジェクトを渡す必要があります。
今回のエラーも加味して簡単に言い換えると、、、
data()
でオプションを何も指定していない場合は、FieldValue.serverTimestamp()
が最終的な値を決定するまでnull
を返します。nullが返されたくないなら、オプションを指定すればなんとかできます。
といったところでしょうか。
ドキュメントを見てみると、data()
のオプションは2つある様です。
オプション | 説明 |
---|---|
estimate | 保留中のサーバータイムスタンプはローカルクロックに基づいた推定値を返します。この推定値は最終的な値とは異なり、サーバーの結果が利用可能になると、これらの値が変更されます。 |
previous | 保留中のタイムスタンプは無視され、代わりに以前の値を返します。 |
none | 省略された場合や 'none' に設定された場合は、サーバの値が利用可能になるまでの間、デフォルトで null が返されます。 |
こんなところに大ヒントが書いてありましたね😅
答えは公式ドキュメントに📃
Firebaseの公式ドキュメントはやはりかなり充実していて、大抵のfirebase関連の問題はドキュメントに書いてあると思いました😅
今後はFirebaseを使うときはわからなかったらドキュメントをしっかり読む、という習慣を身につけようと思いました。