やりたいこと
useRouterによって、query.qに一致するPosts(投稿一覧)を取得&購読したい。
問題
FirestoreにあるPostsコレクションを購読(onSnapshot)したものの、useEffect()の第2引数にuserを入れると無限ループに落ちいった。
import firebase from 'firebase/app';
import { db } from '../lib/firebase'; // export const db したものをimport
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { userState } from '../hooks/authentication'; // key"userState"のatomをimport
import { Post } from '../models/Post'
import Posts from '../components/Posts';
export default function SearchResultShow() {
const user = useRecoilValue(userState);
const [posts, setPosts] = useState<Post[]>(null);
const router = useRouter();
const query = router.query as Query;
useEffect(() => {
console.log('--- Start useEffect ---')
if (query.q === undefined) {
console.log('query.q is undefined');
return;
}
if (!user) {
console.log('user is undefined')
return;
}
// setPosts(null); ←これが原因
const unsubscribe = db.collection('posts')
.where('query', '==', query.q)
.orderBy('createdAt', 'desc')
.onSnapshot((snapshot) => {
console.log('Getting firestore data..')
const posts = snapshot.docs.map((doc) => {
const data = doc.data();
const post = JSON.parse(JSON.stringify(data));
post.id = doc.id;
return post;
});
setPosts(posts);
})
return () => unsubscribe();
}, [query.q, user]);
return (
<Posts posts={posts} />
);
}
原因
useEffect()の中でsetPosts(null)
を呼んでいたのが原因だった。
そもそもReactというのは、useStateによって生成している各Stateが変更されたときにComponentが再描画される。そのためuseEffectも当然再度呼ばれる。
普通はuseEffectの中でsetPosts()
のようにState変更の処理を書いていた場合、setPosts()→postsが変わったので再描画→setPosts→postsが変わったので再描画...と無限ループに陥るので、useEffectの第2引数に[posts]
を入れてやることで前回のpostsと差分があるときだけuseEffectが呼ばれるようになる。
しかし上記コードのようにsetPosts(null)
とsetPosts(posts)
の2つが記述されていた場合、setPosts(null)
→setPosts(posts)
→postsが変わったので再描画→setPosts(null)
→postsが変わったので再描画→setPosts(null)
→setPosts(posts)
→postsが変わったので再描画..のような変なループに陥る。
そもそもsetPosts()されるときはArray.push()
のようにデータを追加しているわけではないので、setPosts(null)を呼ばなくてもpostsは新しいものに丸々すげ替えられる。だからchangeTypeを使った出し分けをしなくてもいいはず。
補足
Recoilを使っていたり、Firebaseのexportの仕方で若干import部分の書き方などが異なると思う(db
はexportせずとも都度firebase.firestore()
で生成してもいい)。