skip to Main Content

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


  1. 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:

    1. 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 from Promise.all(/*...*/).then at all, just do the work in useEffect:

      useEffect(() => {
          // ...save `myState` as JSON...
      }, [myState]); // <== Triggers the effect callback when `myState` changes
      
    2. 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:

      function saveMyState(){
          setMyState((state) => {
              // ...save `state` as JSON...
              return state; // Return state unchanged
          });
      }
      

      I’ve done that a couple of times, but never been really happy with it. 🙂

    3. You can use a ref to always have access to up-to-date state:

      const [myState, setMyState] = useState<myType>({/*...*/});
      const refMyState = useRef(myState); // Using the initial value here just
                                          // makes the types easier
      refMyState.current = myState;       // Make sure the ref is always up-to-date
                                          // (You *could* put the update in a
                                          // `useEffect`, but that's needlessly
                                          // complex.)
      
      // ...
      
      function saveMyState(){
          const state = refMyState.current;
          // ...save `state` as JSON...
      }
      
    4. Use other mechanisms for storing myState rather than useState, such as Redux. (Doing so is a big enough change I won’t do an example, their docs have it covered.)

    Login or Signup to reply.
  2. 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search