21

import React, { useState} from "react";
import ReactDOM from "react-dom";

function App() {
  const [count, setCount] = useState(0);

  function handleAlertClick(){
    return (setTimeout(() => {
  alert("You clicked on: " + count);
}, 3000))
  }


  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
      <button onClick={handleAlertClick}>Show alert</button>
    </div>
  );
}

I just want to know if this works the way that I think it does, or if there is a better explanation!

Whenever the setState method is called the state gets a new reference. This means the original state doesn't have a new value, but we instead create a new state with a new value. When we click on the second button, the event handler function captures the reference of the original state. Even if we click the first button many times, when the alert is displayed it will show the value of the state that the event handler captured its reference.

Is this correct?

petarkolaric
  • 376
  • 3
  • 16
A2ub
  • 264
  • 2
  • 9
  • What issue are you getting? – Tushar Jul 09 '20 at 02:55
  • i just asked if what i had inderstood is correct !! Whenever the setState method is called the state gets a new reference. This means the original state doesn't have a new value, but we instead create a new state with a new value. When we click on the second button, the event handler function captures the reference of the original state. Even if we click the first button many times, when the alert is displayed it will show the value of the state that the event handler captured its reference – A2ub Jul 09 '20 at 04:47

2 Answers2

29

The reason the alert shows the outdated value of count is because the callback passed to setTimeout is referencing an outdated value of count captured by the closure. This is usually referred to as a stale-closure.

On the initial render, the anonymous function passed as a callback to setTimeout captures the value of count as 0, and when the button show alert gets clicked the callback gets queued but with the outdated value of count.

In the case above the easiest solution to show the updated value of count in the alert message and fix the stale-closure issue will be to use a ref.

function App() {
  const [count, setCount] = useState(0);

  const latestValue = useRef(count);

  const handleAlertClick = () => {
    setTimeout(() => {
      alert(`count is: ${latestValue.current}`);
    }, 3000);
  };

  return (
    <div>
      <p>You clicked {count} times</p>
      <button
        onClick={() => {
          setCount(prev => {
            latestValue.current = prev + 1;
            return prev + 1;
          });
        }}
      >
        Click me
      </button>
      <button onClick={handleAlertClick}>Show alert</button>
    </div>
  );
}

Working demo in codesandbox

Hooks rely heavily on closures to work, so it very likely that you may bump into problems regarding stale-closures. Here is a nice article on how stale-closures create issues when using react-hooks and demonstrates how to fix some the issue in some situations.

subashMahapatra
  • 4,919
  • 1
  • 14
  • 21
  • I Don't Think it's About Capturing The value of count, instead it's about capturing the reference of the count variable, do you agree with me ? please read what i said on top below the code in my question ! – A2ub Jul 09 '20 at 04:46
  • It has nothing to do with reference to the state. The behaviour is entirely related to how closure works and how closures can capture an outdated variable. The issue can easily be reproduced in a function. This is not limited to react. See the first example provided in the [article](https://dmitripavlutin.com/react-hooks-stale-closures/) – subashMahapatra Jul 09 '20 at 05:28
  • it's not about capturing outdated variable only, if this the case why the value of value variable steal updated ? it's all about reference the issue is whenever we call the inc() function the message variable initialize with a new adress in the memory, that mean the closure capture the reference of the first message that's why when we call the log function it return the value 1. and if we tried to initialize the message variable on the top with the value variable that will solve the problem because we will not need to initialize the message variable any time we call the inc function !! – A2ub Jul 09 '20 at 05:48
  • Ok i just realized the point that we differ on it when i say the reference i mean that the closure capture the refrence of the variable to return to it whenever it want to use the value of that reference ! what we deal about it is closure doesn't capture the value instead it capture the variable (wich i mean in my case the refrence) !! – A2ub Jul 09 '20 at 06:32
  • Note that `useRef` creates an object that will **persist for the full lifetime of the component** and that React will give you the **same ref object on every render**. This works because by referencing the same object in memory any callback, regardless of its scope when defined or executed, is pointing to the same object which eliminates the stale reference. – jhovanec Feb 19 '21 at 17:17
  • @A2ub I think your assumption seems right. Have you found out the answer to this? I am keen to know that. – Shawn Feb 24 '22 at 05:35
0

https://dmitripavlutin.com/react-hooks-stale-closures/#32-usestate

  function App() {
  const [count, setCount] = useState(0);

  const handleAlertClick = () => {
    setTimeout(() => {
      alert(`count is: ${count}`);
    }, 3000);
  };

  return (
    <div>
      <p>You clicked {count} times</p>
      <button
        onClick={() => {
          setCount((count) => count + 1);
        }}
      >
        Click me
      </button>
      <button onClick={handleAlertClick}>Show alert</button>
    </div>
  );
}