I had a code that was staring a few async function that were modifing my React State. but the problem was that when launching an other function after all promises were resolved, this function did not have the updated version of my State :
const [myState, setMyState] = useState<myType[]>({...});
const pendingPromises: Promise<void>[] = [];
async function asyncFunction(a: myType){
return fs.promises.copyFile(a.currentPath, a.destination).then(() => {
setMyState((myState) => [...myState.filter((b) => (b.id != a.id)), {...a, curentPath: a.destination}]
function saveMyState(){
// Actually just saving it to a JSON
console.log(myState) // This shows the original state before it has been modified by asyncFunction()
}
function mainFunction(){
myArray.forEach((a) => pendingPromises.push(asyncFunction(a))
Promise.all(pendingPromises).then(saveMyState)
}
So to solve the problem I declared a new state that I change to true when I need to call my second function:
const [myState, setMyState] = useState<myType[]>({...});
const [callFunction, setCallFunction] = useState<boolean>(false);
const pendingPromises: Promise<void>[] = [];
useEffect(() => {
if (callFunction) {
saveMyState();
setCallFunction(false);
}
}, [callFunction]);
async function asyncFunction(a: myType){
return fs.promises.copyFile(a.currentPath, a.destination).then(() => {
setMyState((myState) => [...myState.filter((b) => (b.id != a.id)), {...a, curentPath: a.destination}]
});
function saveMyState(){
// Actually just saving it to a JSON file
console.log(myState) // This shows the original state before it has been modified by asyncFunction()
}
function mainFunction(){
myState.forEach((a) => pendingPromises.push(moveFile(a))
Promise.all(pendingPromises).then(() => setCallFunction(true)) // calling function
}
This is working but it doesn’t feels really clean.
Is this way of doing okay ? If not, how should I proceed ?
2
Answers
Often in this situation, the function in question is changing state, so using the callback form of the state setter is the simple solution. But
saveMyState
doesn’t change state, it just uses it.That being the case, it’s probably best not to cause a re-render by setting that state flag just to call the function.
Here are some alternatives:
Since
saveMyState
‘s job is to save the current state as JSON, you might consider just doing that any time the state changes, rather than allowing the JSON version to remain out of sync with the state for a period of time. To do that, don’t call it fromPromise.all(/*...*/).then
at all, just do the work inuseEffect
:You can go ahead and use the callback form of the state setter anyway, just returning the same state it received, making the state update call do nothing:
I’ve done that a couple of times, but never been really happy with it. 🙂
You can use a
ref
to always have access to up-to-date state:Use other mechanisms for storing
myState
rather thanuseState
, such as Redux. (Doing so is a big enough change I won’t do an example, their docs have it covered.)The benefit of a signal is that no matter how many react children you have, only those listening on the signal do a re-render. Everything that includes that component above, whether its a context or useRef, all of its chain will re-render on any change.
If you want to create signals – create an observer on the window object which any react component can listen to. Something like a javascript proxy. Here is an example:
https://github.com/inspiraller/react-proxy