Reputation: 3081
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?
Upvotes: 6
Views: 7796
Reputation: 698
This is not exactly, what you were looking for, but maybe it's good enough:
Instead of waiting for the update, you can just pass the new values to the function explicitly like this:
Change the logging function to this:
const f = (humans, animals) => {
console.log(humans);
console.log(animals);
}
And then simply log the new state independently from the state update:
useEffect(() => {
const newHumans = [ ... ];
const newAnimals = [ ... ];
setHumans(newHumans);
setAnimals(newAnimals);
f(newHumans, newAnimals);
}, []);
This improves readability and was often acceptable behavior for me in the past.
Upvotes: 0
Reputation: 354
If you would like to have your state into a Provider and have your fetching logic somewhere else I use this logic, though technically you don't need to divide your code into multiple files.
//Some Provider
const [humans, setHumans] = useState([]);
const [animales, setAnimals] = useState([]);
//Some Component
const {humans} = useHumanStorage();
const {getHumans} = useGetHumans();
const {animals} = useAnimalStorage();
const {getAnimals} = useGetAnimals();
const [isLoaded, setIsLoaded] = useState<boolean>(false);
const f = (humans, animals) => {
console.log(humans);
console.log(animals);
}
useEffect(() => {
f();
}, [isLoaded]);
useEffect(() => {
let promises: Promise<any>[] = [];
promises.push(getAnimals());
promises.push(getHumans());
Promise.all(promises).then(() => {
setIsLoaded(true);
});
}, []);
Example for the architecture: https://github.com/bespoyasov/frontend-clean-architecture/tree/master/src
You can also do this in one file:
const [isLoaded, setIsLoaded] = useState<boolean>(false);
const [humans, setHumans] = useState([]);
const [animales, setAnimals] = useState([]);
const getAnimals = async () => {
const result = await yourFetchLogic();
setAnimals(result);
}
const getHumans = async () => {
const result = await yourFetchLogic();
setHumans(result);
}
const f = (humans, animals) => {
console.log(humans);
console.log(animals);
}
useEffect(() => {
f();
}, [isLoaded]);
useEffect(() => {
let promises: Promise<any>[] = [];
promises.push(getAnimals());
promises.push(getHumans());
Promise.all(promises).then(() => {
setIsLoaded(true);
});
}, []);
Upvotes: 0
Reputation: 2641
Try to use a custom hook "useStateWithCallback" and perform the state updates in a callback chain.
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];
}
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.
Upvotes: 4