1

Im reading the learning react documentation on the react website and in the Queueing A Series of State Updates section there is a sentence that reads "the UI won't be updated until after your event handles, and any code in it, completes." From this I get the impression that there can only be one render per onClick event but in running the following component it seems the component re-renders twice. Once when adding to the pending state and another when decrementing the pending state and incrementing state inside the setTimeout function.

function RequestTracker() {
  const [pending, setPending] = useState(0);
  const [completed, setCompleted] = useState(0);
  useEffect(() => {
    console.log("MyComponent rendered!");
  });

  function handleClick() {
    setPending((p) => p + 1);
    setTimeout(() => {
      setCompleted((c) => c + 1);
      setPending((p) => p - 1);
    }, 3000);
  }

  return (
    <>
      <h3>Pending: {pending}</h3>
      <h3>Completed: {completed}</h3>
      <button onClick={handleClick}>Buy</button>
    </>
  );
}

Doesn't this contradict the statement that React only renders the UI after the event handler completes all code in it?

Using the useEffect there is indeed more than one render per click. How should I think about how React queues and executes re-renders. Thank you!

qhuboo
  • 15
  • 4
  • 2
    This is likely because of React's strict mode rendering components twice in development (but not production). https://stackoverflow.com/questions/61254372/my-react-component-is-rendering-twice-because-of-strict-mode – scatter Jul 05 '23 at 20:09
  • 2
    Worth noting that renders are driven by changes in state which _may_ occur as the result of an event. An event doesn't cause a render by itself. – Andy Jul 05 '23 at 20:22

1 Answers1

3

the UI won't be updated until after your event handles, and any code in it, completes.

This statement is true. Your handleClick function is completing before the component re-renders.

but in running the following component it seems the component re-renders twice

That's to be expected in this case. Look closely at what the code is doing:

function handleClick() {
  setPending((p) => p + 1);
  setTimeout(() => {
    setCompleted((c) => c + 1);
    setPending((p) => p - 1);
  }, 3000);
}

What does the function handleClick do? Exactly two things:

  1. Call setPending to queue a state update.
  2. Call setTimeout to schedule some function to execute at some later time.

Then it's done. The function has run to completion, performed all of its tasks, and the framework now sees that a state update has been queued and the framework re-renders the component with the new state.

Then, at some later time, this happens:

setCompleted((c) => c + 1);
setPending((p) => p - 1);

Which does exactly two things:

  1. Call setCompleted to queue a state update.
  2. Call setPending to queue a state update.

Once this is done, this operation has completed its logic. The React framework then sees that there are state updates, processes them, and re-renders the component accordingly.


Overall it seems that your confusion is from an expectation that setTimeout is a blocking operation. It isn't. It schedules logic to happen at some later time, and then frees up the JavaScript engine to continue processing.

David
  • 208,112
  • 36
  • 198
  • 279
  • Wow great answer thank you! Follow up question: Does any rendering logic change if I make my handleClick function this, async function handleClick() { setPending(pending + 1); await delay(3000); setPending(pending - 1); setCompleted(completed + 1); } where function delay(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); – qhuboo Jul 05 '23 at 20:30
  • 1
    @qhuboo: Test it and observe the result. But in that test be aware that while `handleClick` may internally `await` an operation, the framework is not going to `await` the call to `handleClick`. So I would suspect that you still may observe two renders, since nothing is blocking. – David Jul 05 '23 at 20:42
  • Same number of renders as the previous code. I should probably dig into the internals. Thank you! – qhuboo Jul 05 '23 at 21:03