0

When I learn React, everyone tells me setState is async because if I console.log(state) right after setState, it will print the old value. However, when I check the source code, useState doesn't return any promise nor use async/await.

Meaning setState must be sync. But if it's sync then why React doesn't re-render the component right away when I call setState? As you can see in the code snippet below, 0 gets printed first, then render1 is printed later.

If this is because of batching then how does batching work? How is it possible to "wait" until specific time to trigger the re-render of App component. And even more important, how does React know that it needs to call App in this case and not other functions?

export default function App(){
  const [count, setCount] = useState(0);
  console.log("render", count);

  return <button onClick={() => {
     setCount(count+1); // why doesn't App get called/re-rendered right away here
     console.log(count); // this prints the old value first then re-render happens later
   }}>
  </button>
}
Light
  • 11
  • 2
  • `However, when I check the source code, useState doesn't return any promise nor use async/await.` Async code does not necessarily have to use Promises. JavaScript was asynchronous _long_ before Promises were even a thing. – tkausl Aug 01 '22 at 14:21
  • Does this answer your question? [Why is setState in reactjs Async instead of Sync?](https://stackoverflow.com/questions/36085726/why-is-setstate-in-reactjs-async-instead-of-sync) – Matt Morgan Aug 01 '22 at 14:27

1 Answers1

0

First, I would caution you that the reason why console.log(count) logs out the old value has nothing to do with sync vs async. The reason why you log out the old value is that count is a local const which will never change. It started its existence as the old value, and it will always be the old value no matter how much time passes, and no matter the order of operations.

Calling setCount does not change the value in count, it just asks react to rerender the component. When that new render happens, a new set of local variables will be created, with the new values. But your console.log from the previous render can't interact with values in the next render.


That said, it is true that rendering in react is (usually) delayed briefly to batch up multiple changes. The exact way they do this i'm not sure, because there's several possibilities. I've previously looked through their code, but enough has changed in the last few years that i doubt what i found will still be useful. In any event, ways that they could batch the rerender include:

  1. setTimeout. The first time you set a state, they could set a timeout to go off soon. If another set state happens, they take note of the state, but don't bother setting an additional timeout. A little later, the timeout will go off and the rendering happens.

  2. A microtask. The easiest way to do this is Promise.resolve().then(/* insert code here */). Once the current call stack finishes executing, microtasks will run. This approach has the benefit that it can happen sooner than a setTimeout, since timeouts have a minimum duration

  3. If execution began inside react, they could just wait until your code returns, then do the render. This used to be the main way react batched changes. Eg, they had a piece of code responsible for calling your useEffects. During that, it would take note of state changes, but not rerender yet. Eventually you return, and since returning puts code execution back in react's hands, it then checks which states have changed and rerenders if needed.

how does React know that it needs to call App in this case and not other functions?

It knows that because the state that you changed is a state of App. React knows the tree structure of your components, so it knows it only needs to render the component where you set state, plus its children.

Nicholas Tower
  • 72,740
  • 7
  • 86
  • 98
  • I searched for setTimeout in React source code and it indeed appears. I can't really tell what it's for yet but seems like you're correct. "It knows that because the state that you changed is a state of App" I call useState without passing any hint whatsoever so how does React knows which useState associate with which component? – Light Aug 01 '22 at 15:38
  • `I call useState without passing any hint whatsoever so how does React knows which useState associate with which component?` You don't need to pass a hint, because react called **you**. React decides "ok, time to render the App component", and then it calls your App function. Until you return, any calls you make to useState will be states of the App component. – Nicholas Tower Aug 01 '22 at 16:06