0

I am creating a simple agenda application that is hooked up to firebase as a backend. I want to fetch the data on every write and I am trying to dodge an infinite loop.

useEffect(() => {
    const getTodos = () => {
      console.log("I will run");
      db.collection("Users")
        .doc(user.email)
        .collection("Todos")
        .get()
        .then((snapshot) => {
          const loadedTodos = snapshot.docs.map((docs) => {
            return {
              todo: docs.data().todo,
              isCompleted: docs.data().isCompleted,
              id: docs.id,
            };
          });
          setTodos(loadedTodos ?? []);
        });
    };
    getTodos();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [todos]);

The initial todos state is null. However, when I fetch the firebase data, it changes and then causes a rerender on the component itself which causes another fetch again causing an infinite loop. I have broke the limit of the spark plan in firebase because of this and wasted days of work because of temporary blocks. I think this can be fixed with the useCallback hook, but I do not know how. Also, I want to add the same functionality with a chat application (where data will be written from two sides). Is it possible to rerender only when one of the two sides has written or does it have to be an infinite loop in such a case?

Ruli
  • 2,592
  • 12
  • 30
  • 40
Ahmad Shaker
  • 15
  • 2
  • 5
  • 2
    This behavior is expected, since you've kept a state variable in dependency array of a function and inside the function you're trying to update the state variable. Have you tried keeping the dependency array blank so that the function gets fired only at the component mount? i.e, instead of `[todos]` , write `[ ]` – Areeb Khan Jul 19 '21 at 11:25
  • https://stackoverflow.com/questions/53715465/can-i-set-state-inside-a-useeffect-hook – NeERAJ TK Jul 19 '21 at 11:26
  • This makes it run only one time, I do not want that, I want it to run everytime a user uses the addTodo function which adds a todo or the setCompletion state function. – Ahmad Shaker Jul 20 '21 at 15:37
  • @AhmadShaker - show more of your code. What it sounds like is that you need to call `getTodos` multiple times - once when the user is logged in and whenever the todos are updated. Could you show more of your component code - specifically when the todos are updated (setCompletion/addTodo) – Adam Jenkins Jul 20 '21 at 15:45

2 Answers2

2

The dependency you want in your effect is user.email

useEffect(() => {
   const getTodos = () => {
     console.log("I will run");
     db.collection("Users")
       .doc(user.email)
       .collection("Todos")
       .get()
       .then((snapshot) => {
         const loadedTodos = snapshot.docs.map((docs) => {
           return {
             todo: docs.data().todo,
             isCompleted: docs.data().isCompleted,
             id: docs.id,
           };
         });
         setTodos(loadedTodos ?? []);
       });
   };
   getTodos();
 }, [user.email]);

You can read this as: every time user.email changes, I will want to refetch the to-do list.

EDIT: The reason you are observing an infinite loop, as others have said, is because your effect's dependency gets changed by the effect itself, which causes the effect to run again. This can often be fixed by "lifting state up" - but in this case, it's because you're using the wrong dependency.

Ruli
  • 2,592
  • 12
  • 30
  • 40
Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100
  • The user email is not supposed to change, if they are logged in, so I do not think this correct way to do it. – Ahmad Shaker Jul 20 '21 at 15:37
  • @AhmadShaker Just because the user email is not supposed to change doesn't mean this isn't the correct way to do it. `user.email` **is a dependency** of this effect. That is all. – Adam Jenkins Jul 20 '21 at 15:40
0

I just added a new useState hook, shouldComponentRerender, that changes whenever a function of (addTodos, deleteTodo, setCompletionState) is called, and then the shouldComponentRerender state was added as a dependency for the useEffect hook. Gave me the result I wanted. Thanks!

Ahmad Shaker
  • 15
  • 2
  • 5