Home > Back-end >  React: Why the count values are different in useEffect and inc event handler?
React: Why the count values are different in useEffect and inc event handler?

Time:12-13

In the below code snippet, the component is rendered thrice when user clicks on the button but the count values remain the same.

import React, { useEffect } from 'react';

const wait = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, 2000);
  });

export default function App() {
  const [count, setCount] = React.useState(0);

  useEffect(() => {
    console.log('rerendered');
    console.log(count);
  });

  const inc = async () => {
    setCount(count   1);
    setCount(count   5);

    await wait();

    console.log(count);
    setCount(count   1);
    console.log(count);
    setCount(count   2);
    console.log(count);
  };

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={inc}>Increment</button>
    </div>
  );
}

I know that first two setCount() are batched and after await they are not batched. If I use setCount(prev => prev 1), I will get the updated values but my question is

Why are the count values different in inc() and useEffect()? When does the count value actually changes coz certainly not on re-render?

CodePudding user response:

Short explanation:

The count value of 0 is closed over the inc function at the moment of execution (i.e. the moment the user pressed the Increment button). This is because the inc function opens a console log output

Explanation:

  1. "rerendered - useEffect 0": useEffect is called on mount of the component with the initial value 0 of the count state.
  2. "rerendered - useEffect 5": Right after pressing Increment the two batched setCount calls cause the value to be 5 and re-render.
  3. Now the inc function waits for 2 seconds.
  4. inc 1 0: After 2 seconds the async function continues its execution and this log is called.
  5. inc 2 0: The setCount call right after inc 1 0 is called before this console.log statement, but executed asynchronously, so it will run after all synchronous tasks (this log), but before the next asynchronous task.
  6. "rerendered - useEffect 1": setCount caused a re-render. 2 is shown in the UI and the useEffect is executed with this new value 2. Why is the value not 7 (i.e. 5 2)? - Because the inc function closes over the count value at the moment its called (i.e. when the user pressed the Increment button). Even new re-renders won't alter the count inside of the closure.
  7. inc 4 0: Execution continues and the local "frozen" count value gets printed to the screen.
  8. "rerendered - useEffect 1": Same explanation as point 6.

Play around with the code in this CodeSandBox

Also check out this great article about the useEffect hook which explains its relation with JavaScript closures really well.

  • Related