Search code examples
reactjsasynchronoususe-effectrerender

ReactJS - Inefficient useEffect runs four times


I have a useEffect function that must wait for four values to have their states changed via an API call in a separate useEffect. In essence the tasks must happen synchronously. The values must be pulled from the API and those stateful variables must be set and current before the second useEffect can be called. I am able to get the values to set appropriately and my component to render properly without doing these tasks synchronously, I have a ref which changes from true to false after first render (initRender), however I find the code to be hacky and inefficient due to the fact that the second useEffect still runs four times. Is there a better way to handle this?

  //Hook for gathering group data on initial page load
  useEffect(() => {
    console.log("UseEffect 1 runs once on first render");
    (async () => {
      const response = await axios.get(`${server}${gPath}/data`);
      const parsed = JSON.parse(response.data);
      setGroup(parsed.group);
      setSites(parsed.sites);
      setUsers(parsed.users);
      setSiteIDs(parsed.sitesID);
      setUserIDs(parsed.usersID);
    })();
    return function cleanup() {};
  }, [gPath]);

  //Hook for setting sitesIN and usersIN values after all previous values are set
  useEffect(() => {
    console.log("This runs 4 times");
    if (
      !initRender &&
      sites?.length &&
      users?.length &&
      userIDs !== undefined &&
      siteIDs !== undefined
    ) {
      console.log("This runs 1 time");
      setSitesIN(getSitesInitialState());
      setUsersIN(getUsersInitialState());
      setLoading(false);
    }
  }, [sites, siteIDs, users, userIDs]);

EDIT: The code within the second useEffect's if statement now only runs once BUT the effect still runs 4 times, which still means 4 renders. I've updated the code above to reflect the changes I've made.

LAST EDIT: To anyone that sees this in the future and is having a hard time wrapping your head around updates to stateful variables and when those updates occur, there are multiple approaches to dealing with this, if you know the initial state of your variables like I do, you can set your dependency array in a second useEffect and get away with an if statement to check a change, or multiple changes. Alternatively, if you don't know the initial state, but you do know that the state of the dependencies needs to have changed before you can work with the data, you can create refs and track the state that way. Just follow the examples in the posts linked in the comments.

I LIED: This is the last edit! Someone asked, why can't you combine your different stateful variables (sites and sitesIN for instance) into a single stateful variable so that way they get updated at the same time? So I did, because in my use case that was acceptable. So now I don't need the 2nd useEffect. Efficient code is now efficient!


Solution

  • Your sites !== [] ... does not work as you intend. You need to do

    sites?.length && users?.length
    

    to check that the arrays are not empty. This will help to prevent the multiple runs.