React: useContext associated with a custom hook infinite loop issue

3k views Asked by At

I am creating a todolist app paired with Google Firebase to store my data. In the app, you can create projects to organise your tasks. I am using useContext to have my projects accessible accross all the components of my app. My context uses a custom hook to make a call to firebase when mounting to get the projects of the logged in user (or an empty array if the user hasn't got any projects stored in firebase).

My issue is that my contextProvider goes into a mounting loop, making a new call to firebase to set the projects every time it mounts (even though the projects are not changing). The context is actually working in my app, I only realised it was looping because I have reached the maximum amount of calls that can be made to firebase on the freeplan in a day.

Here's my code for for the context:

import React, { createContext, useContext } from "react";
import PropTypes from "prop-types";
import { useProjects } from "../Hooks/index";

export const ProjectsContext = createContext();
export const ProjectsProvider = ({ children }) => {
  const { projects, setProjects } = useProjects();

 
  return (
    <ProjectsContext.Provider value={{ projects, setProjects }}>
      {children}
    </ProjectsContext.Provider>
  );
};

export const useProjectsValue = () => useContext(ProjectsContext);

and here is my code for the custom hook useProjects() that makes a call to firebase to retrieve the projects:

export const useProjects = () => {
  const [projects, setProjects] = useState([]);

  useEffect(() => {
    db.collection("projects")
      .where("userId", "==", "uBnTwu5ZfuPa5BE0xlkL")
      .orderBy("projectId")
      .get()
      .then((snapshot) => {
        const allProjects = snapshot.docs.map((project) => ({
          ...project.data(),
          docId: project.id,
        }));

        if (JSON.stringify(allProjects) !== JSON.stringify(projects)) {
          setProjects(allProjects);
        }
      });
  }, [projects]);

  return { projects, setProjects };
};

Also, I am console logging in the hook and in the context to check how many times it is beeing called, is it good practice or is there a better way to check how many times a component mounts ?

Thanks in advance

1

There are 1 answers

1
Yousaf On BEST ANSWER

Your code gets stuck into a loop because the useEffect hook contains projects as a dependency. As the code in the useEffect updates projects, your code gets stuck in a endless cycle of state update and re-render.

Looking at your code, it seems that useEffect uses projects in the if statement

if (JSON.stringify(allProjects) !== JSON.stringify(projects)) {
   setProjects(allProjects);
}

and that seems like something you shouldn't be doing in the first place.

You could observe the projects collection if you always want to receive real-time updates, otherwise the useEffect hook should only run once.

useEffect uses setProjects() function but it is guaranteed to not change, so adding or omitting it from the dependency array of the useEffect hook won't make a difference.