reactjssettimeoutrerender

When does re-rendering actually happens?


Code:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setTimeout(()=>setNumber(number + 1),2000);
        setTimeout(()=>setNumber(number + 1),5000);
      }}>+3</button>
    </>
  )
}

I went through this saying Setting state only changes it for the next render. During the first render, number was 0. This is why, in that render’s onClick handler, the value of number is still 0 even after setNumber(number + 1) was called and this question already, so some understanding that I have is these calls are async hence the other code continues to run. But my doubt is when will re-rendering happen? For example in my code I know that making change as number=>number+1 will give the desired result but shouldn't after setNumber(number + 1); this line re-render should occur but the next two lines are still in time out so they are still waiting, and when the rendering due to setNumber(number + 1); completes shouldn't they should see new value of number? What am I missing here?

Now next thing, say these all three lines are queued in some re-rendering queue, there will be re-rendering when the first queue entry resolves and then the next one sees the updated number value, why doesn't even this happens? And why does number=>number+1 solves the issue?


Solution

  • For example in my code I know that making change as number=>number+1 will give the desired result but shouldn't after setNumber(number + 1); this line re-render should occur but the next two lines are still in time out so they are still waiting, and when the rendering due to setNumber(number + 1); completes shouldn't they should see new value of number? What am I missing here?

    number is a snapshot of what the state was when this component rendered. As a const, it can never change. Even if you changed your code to let, calling setNumber does not even attempt to change your local variables, so it would still not change. So assuming number is currently 0, if you call setNumber(number + 1) three times, then you are calling setNumber(1) three times. You never call setNumber(2).

    When you change it to setNumber(number => number + 1), you are no longer using your local variable, so the fact that it never changes doesn't matter. React knows what the state actually is and will pass it into you. You can then calculate what the new state should be from that and return it. So the first time react passes in 0 and you return 1. Then react passes in 1 and you return 2, and finally react passes in 2 and you return 3.

    In case the similar names are confusing things, note that the number in the number => number + 1 is not the same thing as the number in const [number, // .... You could rename the function argument to make this clearer, such as setNumber(prev => prev + 1). prev is a new snapshot of the state which react passes into you.

    But my doubt is when will re-rendering happen?

    I skipped this question because it kind of doesn't matter; the issue has entirely to do with stale closure variables, not with timing. But as for when the re-render happens: react waits until the current javascript execution stack finishes, and then if 1 or more states have changed, it does the render. You can think of it as though react is doing a setTimeout(rerender, 0) (though i'm not sure that's actually how it's implemented -- they may be using a microtask)