简体   繁体   中英

Closure when reference to function state using useState Hook

This code is from react document:

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

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // This effect depends on the `count` state
    }, 1000);
    return () => clearInterval(id);
  }, []); // 🔴 Bug: `count` is not specified as a dependency

  return <h1>{count}</h1>;
}

And they said that:

The empty set of dependencies, [], means that the effect will only run once when the component mounts, and not on every re-render. The problem is that inside the setInterval callback, the value of count does not change, because we've created a closure with the value of count set to 0 as it was when the effect callback ran. Every second, this callback then calls setCount(0 + 1), so the count never goes above 1.

But isn't the function in setInterval is closure so its count variable reference to count state of Counter, when setCount run Count will change and so function in setInterval must be able to get new value of count? Am i missing something?

But isn't the function in setInterval is closure so its count variable reference to count state of Counter, when setCount run Count will change and so function in setInterval must be able to get new value of count?

Not really. Closures close over individual variables, or variable environments. With React, when a component re-renders, the whole function runs again, creating new bindings for each variable inside.

For example, the first time Counter runs, it does:

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

creating those two variables. Since the useEffect has an empty dependency array, its callback will only run once, on mount, so those two variables are what its closure will reference.

But then once the state changes, and the component re-renders, the function runs this line again:

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

creating those two new variables again, in a new scope. This new count will be incremented, but the interval callback will not have closed over it.

Here's a live snippet that demonstrates the issue in vanilla JS:

 let i = 0; const getRenderValue = () => i++; const Component = () => { const thisRenderValue = getRenderValue(); console.log('Rendering with thisRenderValue of', thisRenderValue); if (thisRenderValue === 0) { setInterval(() => { console.log('Interval callback sees:', thisRenderValue); // Run component again: Component(); }, 1000); } }; Component();

You should use prev state value for updating.

  useEffect(() => {
    const id = setInterval(() => {
      setCount((prev) => prev + 1);
    }, 1000);
    return () => clearInterval(id);
  }, [count]);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM