Strange behavior of React hooks: delayed data update
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>
The state update is asynchronous so if you are using Hooks you can use useEffect
to be notified after an update.
Here is an example:
https://codesandbox.io/s/wxy342m6l
If you are using setState of a React component, you can use the callback
this.setState((state) => ({count: !state.count}), () => console.log('Updated', this.state.count));
Remember to use the callback to update state if you need a state value.
The setState
hook doesn't update the value in the component directly. It updates it's internal state, causes a rerender of the component, and then returns the new state value, which you assign to count
.
When you console.log(count)
inside test
, you display the current value of count
, which is the old value (before the update). If you'll move the console.log()
to the render, you'll see the new value:
const { useState } = React;
const Component = () => {
const [count, setCount] = useState(false);
const test = () => {
console.log('before: ', count);
setCount(!count)
}
console.log('after: ', count);
return (
<div className="App">
<button onClick={test}>Click</button>
</div>
);
}
ReactDOM.render(
<Component />,
demo
);
<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="demo"></div>