はじめに
同じミスを繰り返してしまったため、今後の参考として記録しておきます。
やろうとしていたこと
Cloud Firestoreからデータを取得し、そのデータをsetPostList関数を使ってpostListに保存し、画面に出力しようとしていました。
最初に書いていたコード
以下は、Cloud Firestoreからデータを取得するために私が書いていたコードです。
const [postList, setPostList] = useState([]);
useEffect(() => {
const getPosts = async () => {
const querySnapshot = await getDocs(collection(db, "posts"));
querySnapshot.forEach((doc) => {
setPostList(doc.data());
});
}
getPosts();
}, [])
起きたこと
postListが空の配列として出力され、期待した情報を取得できませんでした。
問題の原因
以下のコードが原因でした。
querySnapshot.forEach((doc) => {
setPostList(doc.data());
});
何が起きていたのか
ReactのsetState(今回の場合はsetPostList)関数は、状態を上書きする動作をします。そのため、ループ内でsetPostListを繰り返し呼び出すと、最後に処理された値だけがpostListに残ることになります。
さらに、setPostListは非同期的に動作するため、タイミングのズレが生じ、意図したデータが保存されないことがあります。
このため、今回は空配列となってしまいデータを正しく取得することができませんでした。
正しい修正方法
以下のように修正することで、正しくデータを保存できます。
①新しい配列を作成する
②setPostListに直接値を代入するのではなく、まとめて更新を行う
useEffect(() => {
const getPosts = async () => {
const querySnapshot = await getDocs(collection(db, "posts"));
const posts = [];
querySnapshot.forEach((doc) => {
posts.push(doc.data()); // 配列に追加
});
setPostList(posts); // 最後に状態を更新
};
getPosts();
}, []);
リファクタリング
forEachの代わりにmapを使うと、新しい配列を返してくれるので、この方法だとよりシンプルに書けるなと思いました。
useEffect(() => {
const getPosts = async () => {
const querySnapshot = await getDocs(collection(db, "posts"));
const posts = querySnapshot.docs.map(doc => doc.data()); // mapを使って配列を作成
setPostList(posts); // 最後に状態を更新
};
getPosts();
}, []);
まとめ
- ReactのsetState関数は上書き動作をするため、ループ内で直接呼び出すと正しくデータが保存されない。
- 配列を使ってデータをまとめて管理することで、状態を一度に更新するようにすることが大切。
参考
今回コードを書くにあたり、こちらの動画を受講しています。
firebaseとReactの連携をわかりやすく教えてくださる素晴らしい動画でした!
ありがとうございました。
また記事執筆にあたり、こちらのリソースを参考にしています。