简体   繁体   中英

React: Updated state not reflecting in recursive useCallback

I am incrementing foo in a useCallback hook called recursively . But, the incremented foo won't reflect in the callback.

 const { useState, useCallback } = React; const App = () => { const [foo, setFoo] = useState(0); const recursiveCallback = useCallback(() => { // always logs 0 console.log(foo); // incrementing foo setFoo(foo + 1); setTimeout(() => { // recursive call recursiveCallback(); }, 250); }, [foo]); return <button onClick={recursiveCallback}>Click Me</button>; } ReactDOM.render(<App />,document.getElementById('root'));
 <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

You are having a closure on foo===0 value, both in the log and in the setter function, to fix it you need to use functional update and useEffect for logging:

 const { useState, useCallback, useEffect } = React; const App = () => { const [foo, setFoo] = useState(0); const recursiveCallback = useCallback(() => { setFoo(prev => prev + 1); setTimeout(() => { recursiveCallback(); }, 250); }, [foo]); useEffect(() => { console.log(foo); }, [foo]); return <button onClick={recursiveCallback}>Click Me</button>; } ReactDOM.render( < App/>, document.getElementById('root'));
 <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

  1. You should use useEffect() with [foo] dependency to see the changes after the state has been set.
  2. The function to update foo should be a Functional update

If the new state is computed using the previous state, you can pass a function to setState The function will receive the previous value , and return an updated value

    // incrementing foo
    setFoo(previousFoo => previousFoo + 1);

Note: what happened if you did not choose functional update ?

Well, if you chose setFoo(foo + 1); , the next sate will be 2 only, after that, the state is not changed, useEffect with dependency foo is also not affected as well.

In short , if you need a previous state, you have to use functional update .

 const { useState, useCallback, useEffect } = React; const App = () => { const [foo, setFoo] = useState(0); const recursiveCallback = useCallback(() => { // incrementing foo //setFoo(foo + 1); setFoo(previousFoo => previousFoo + 1); setTimeout(() => { // recursive call recursiveCallback(); }, 500); }, [foo]); useEffect(() => { console.log(foo); }, [foo]); return <button onClick={recursiveCallback}>Click Me</button>; } ReactDOM.render(<App />,document.getElementById('root'));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script> <div id="root"></div>

Try changing this line

setFoo(foo + 1);

to

setFoo((foo) => foo + 1);

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