3

I have the following states

const [humans, setHumans] = useState([]);
const [animales, setAnimals] = useState([]);

And the following function

const f = () => { 
   console.log(humans);
   console.log(animals);
}

which only has to be executed when the following useEffect finishes updating both states

useEffect(() => {
   setHumans([ ... ]);
   setAnimals([ ... ]);
   f();
}, []);

How can I make sure that f is executed after both asynchronous state updates has finished?

Raul
  • 2,373
  • 7
  • 26

2 Answers2

1

Try to use a custom hook "useStateWithCallback" and perform the state updates in a callback chain.

useStateWithCallback

import { useState, useRef, useEffect } from "react";

export default function useStateWithCallback(initialState) {
  const [state, setState] = useState(initialState);

  const callbackRef = useRef(null);

  const setStateCallback = (state, callback) => {
    callbackRef.current = callback; // store passed callback to ref
    setState(state);
  };

  useEffect(() => {
    if (callbackRef.current) {
      callbackRef.current(state);
      callbackRef.current = null; // reset callback
    }
  }, [state]);

  return [state, setStateCallback];
}

Code

const [humans, setHumans] = useStateWithCallback([]);
const [animals, setAnimals] = 
useStateWithCallback([]);

const logStates = () => {
   console.log(humans);
   console.log(animals);
}

const updateStates = () => {
   return new Promise((resolve) => {
      setHumans([...], () => {
         setAnimals([...], resolve);
      }
   });
}

useEffect(() => {
   (async () => {
      await updateStates();
      logAnimals();
   })();
}, []);

With this example, you will be able to use the functionality of "waiting for multiple state updates before doing something" outside useEffect too, like this:

const otherFunction = async () => {
    await updateStates();
    doSomething();
}

If you have problems with not up-to-date states, then wrap the method in a useCallback, adding the states to the deps array.

Victor Molina
  • 2,471
  • 9
  • 32
  • Also, you can use useStateWithPromise https://stackoverflow.com/questions/53898810/executing-async-code-on-update-of-state-with-react-hooks – Victor Molina Mar 21 '21 at 12:46
1

This is not exactly, what you were looking for, but maybe it's good enough:

You could change the logging function to this:

const f = (humans, animals) => { 
   console.log(humans);
   console.log(animals);
}

And then simply log the new state before the actual state was updated:

useEffect(() => {
   const newHumans = [ ... ];
   const newAnimals = [ ... ];
   setHumans(newHumans);
   setAnimals(newAnimals);
   f(newHumans, newAnimals);
}, []);

This improves readability and was often acceptable behaviour for me in the past.

cwallenwein
  • 390
  • 3
  • 17