A2ub
A2ub

Reputation: 434

How To Solve The React Hook Closure Issue?


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 instead creates 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?

Upvotes: 37

Views: 18532

Answers (3)

bored_coffee
bored_coffee

Reputation: 5

To everyone confused about this. Here is my 2 cents on the matter. For the code given in the question we don't have a stale closure because on every render 'function handleAlertClick' is reinitialised and so is the internal timeout function. Hence it gets the latest state value.

Instead if you have the timeout function in useEffect hook(check codesandbox), you will get the stale closure issue which can be solved using useref.

Now the big question why is the stale closure issue there for state variable and not with useref. I feel this is because ref object stays same on every render. Only the current property value changes. While for every new render the state variable gets changed(because we are changing it) So the timeout function references the original state variable. For new subsequent renders the state variable is different(stored in different area in memory)

Working demo: https://codesandbox.io/p/sandbox/vigorous-frog-lry3n6?file=%2Fsrc%2Findex.js%3A13%2C27

Referred this : Why does useRef solve the problem of stale state?

Upvotes: 0

Volodymyr Buts
Volodymyr Buts

Reputation: 7

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>
  );
}

Upvotes: -1

subashMahapatra
subashMahapatra

Reputation: 6837

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.

Upvotes: 50

Related Questions