'In React useEffect, how do you sneak state updates in between long running code?

I'm having trouble getting my loading status to appear before the longRunningCode executes. I tried making it async to no avail.

const [loadingStatus, setLoadingStatus] = useState(undefined);
const [myAction, setMyAction] = useState(undefined);

const longRunningCode = () => {
  const file = // synchronous code to generate a gzipped File object
  return file;
}

// also tried
// const longRunningCode = async () => {
// ...

useEffect(() => {
  if (myAction) {
    setLoadingStatus('Do the thing...')
    const result = longRunningCode()
    // also tried `await` with async version
    // ...
    setLoadingStatus(undefined)
    setMyAction(undefined)
  }
}, [myAction])

//...

return (
  <div>
    <p>{loadingStatus}</p>
    <button onClick={() => setMyAction({})}>Generate file</button>
  </div>
)


Solution 1:[1]

useEffect callbacks are only allowed to return undefined or a destructor function. In your example, you have passed an async function which returns a Promise. It may or may not run, but you will see React warnings that useEffect callbacks are run synchronously.

Instead, define an async function, and then call it.

const [loadingStatus, setLoadingStatus] = useState(undefined);
const [myAction, setMyAction] = useState(undefined);

useEffect(() => {
  const doAction = async () => {
    if (myAction) {
      setLoadingStatus('Do the thing...');
      const result = await longRunningCode();
      // ...
      setLoadingStatus(undefined);
      setMyAction(undefined);
    }
  };

  doAction();
}, [myAction]);

//...

return <p>{loadingStatus}</p><button onClick={() => setMyAction({})} />

Otherwise, what you have written will work.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Benjamin