Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behavior of React hooks: delayed data update

Strange behavior: I expect that the first and the second console.log display a different result, but they display the same result and the value is changed only on the next click. How should I edit my code so that the value will be different?

function App() {
  const [count, setCount] = React.useState(false);
  const test = () => {
    console.log(count); //false
    setCount(!count);
    console.log(count); //false
  };
  return (
    <div className="App">
      <h1 onClick={test}>Hello StackOverflow</h1>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

You can see a working example here: https://codesandbox.io/s/wz9y5lqyzk

like image 635
befreeforlife Avatar asked May 04 '19 13:05

befreeforlife


1 Answers

State updates are asynchronous. The setCount function you get from useState can't magically reach out and change the value of your count constant. For one thing, it's a constant. For another, setCount doesn't have access to it. Instead, when you call setCount, your component function will get called again, and useState will return the updated count.

Live Example:

const {useState} = React;

function Example() {
  const [count, setCount] = useState(false);
  const test = () =>{
    setCount(!count); // Schedules asynchronous call to Example, re-rendering the component
  };
  return (
    <div className="App">
      <h1 onClick={test}>Hello CodeSandbox</h1>
      <div>Count: {String(count)}</div>
    </div>
  );
}

ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

If you need to perform some side-effect when count changes, use useEffect:

useEffect(() => {
  console.log(`count changed to ${count}`);
}, [count]);

Notice that we tell useEffect to only call our callback when count changes, so that if we have other state, our callback doesn't run unnecessarily.

Live Example:

const {useState, useEffect} = React;

function Example() {
  const [count, setCount] = useState(false);
  const test = () =>{
    setCount(!count); // Schedules asynchronous call to Example, re-rendering the component
  };
  useEffect(() => {
    console.log(`count changed to ${count}`);
  }, [count]);
  return (
    <div className="App">
      <h1 onClick={test}>Hello CodeSandbox</h1>
      <div>Count: {String(count)}</div>
    </div>
  );
}

ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
like image 71
T.J. Crowder Avatar answered Nov 06 '22 10:11

T.J. Crowder