0

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!

TheFunk
  • 981
  • 11
  • 39
  • 1
    You could do a similar check for the other two dependencies and would run only when all of them are populated. Alternatively you can go a bit more evolved and do something similar to the approaches here https://stackoverflow.com/questions/56881640/run-effect-hook-only-when-both-dependencies-change. Besides, for first run do you really need the gPath? or would you be fine just passing an empty array as a dependency for the useEffect to run only once? – mitomed Dec 09 '20 at 00:07
  • @mitomed unfortunately the userIDs and siteIDs can be empty. They represent the users in the group, but the group can have zero users/sites. So I really need the code to run synchronously. – TheFunk Dec 09 '20 at 00:09
  • @mitomed If it's placed outside of the useEffect, is useLocation guaranteed to run before useEffect? I am open to exploring other ways of getting my relative path if the dependency in the first useEffect is causing issue with my order of operations. – TheFunk Dec 09 '20 at 00:14
  • 1
    Then look at the examples given on that question and similar ones, https://stackoverflow.com/questions/62974717/useeffect-when-all-dependencies-have-changed esentially you keep track of the previous value of the dependencies – mitomed Dec 09 '20 at 00:15
  • @mitomed ouch. async-first programming is painful. I removed the initial state for the userIDs and siteIDs, now the code within the if statement only runs once, which is good, but the useEffect itself is still going to run 4 times. I will look at the posts you suggested. If you'd like to post as an answer I think I'm on the way to solving this now and I'd like to give you credit. – TheFunk Dec 09 '20 at 00:26
  • @mitomed I guess I have one more question because all of the other answers in the links seem to use useEffects in response to multiple renders. Does useEffect by itself, not trigger a rerender? – TheFunk Dec 09 '20 at 00:54
  • @mitomed thank you for helping me get on the right track. – TheFunk Dec 09 '20 at 01:28
  • sorry if I couldn't help more, I can think that if you were to use a state to keep track of all those async calls coming back then you could use just that one as a dependency for running the second effect only once – mitomed Dec 09 '20 at 08:56

1 Answers1

1

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.

Vlad L
  • 1,544
  • 3
  • 6
  • 20
  • Sorry, my JavaScript-fu is still a little weak. That helped, we're down to 3 passes now. – TheFunk Dec 09 '20 at 00:01
  • Ideally the code in the second useEffect should only run one time total and I shouldn't need to check that those other values are already set with the if statement at all. – TheFunk Dec 09 '20 at 00:07
  • 1
    Why are you worried about the if statement executing 4 times? I don't think it's equivalent to 4 re-renders, more correct to say it's equivalent to React checking what if anything it needs to re-render 4 times, which is not the end of the world. You could create a new smart object which verifies that it has all the data it needs, and then sets the update flag which triggers the effect. However the checking would still need to happen within that object if it has everything it needs. – Vlad L Dec 09 '20 at 01:07
  • I guess coming from a synchronous object oriented background I hate the idea of anything doing anything more times than it needs to. If this is the equivalent of React doing next to nothing four times, that's okay but it just feels so wrong. – TheFunk Dec 09 '20 at 01:14
  • This is the whole point of declarative style :) declare what you need to happen and let react handle the how. You can think about optimising if you see a performance problem. – Vlad L Dec 09 '20 at 01:22