3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

useEffectの中のonSnapshot()で無限ループに陥る問題

Posted at

やりたいこと

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()で生成してもいい)。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?