consider this code:
const {useState} = React;
function App() {
const [count, setCount] = useState(0);
const onClick = () => {
setCount((prevCount) => {
console.log(prevCount + 1);
return prevCount + 1;
});
setCount((prevCount) => {
console.log(prevCount + 1);
return prevCount + 1;
});
setCount((prevCount) => {
console.log(prevCount + 1);
return prevCount + 1;
});
console.log("onclick");
};
console.log("rendering");
return <button onClick={onClick}> Increment {count} </button>;
}
ReactDOM.render(<App/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
When run and the button is clicked, this is the output it produces:
1
onclick
2
3
rendering
I would have expected the output to be
onclick
1
2
3
rendering
as I am using the updater function to access previous stat and state updates are batched and async by default.
And expectedly so, further click on the button confirm this, and produce this output:
onclick
4
5
6
rendering
I suspect that the first set state is always synchronous in case of hooks, because if I change my code to this:
function App() {
const [count, setCount] = useState(0);
const onClick = () => {
// add this extra set state before any other state updates
setCount(1);
setCount((prevCount) => {
console.log(prevCount + 1);
return prevCount + 1;
});
setCount((prevCount) => {
console.log(prevCount + 1);
return prevCount + 1;
});
setCount((prevCount) => {
console.log(prevCount + 1);
return prevCount + 1;
});
console.log("onclick");
};
console.log("rendering");
return <button onClick={onClick}> Increment {count} </button>;
}
the output as expected is:
onclick
2
3
4
rendering
I haven't been able to find any explanations for this online yet. This would not impact any functionality as much as I can see, because although the first time it is executed synchronously it still updates the state asynchronously, which means I can't access the updated state in console.log("onclick" + count)
Would be helpful to get an explanation of why it works like this.
NOTE: discussed this on github. Seems like this is one of the things that as consumers we should not care about. It is an implementation detail. https://github.com/facebook/react/issues/19697
React do not update immediately, although it seems immediate at first glance.
TL;DR: useState is an asynchronous hook and it doesn't change the state immediately, it has to wait for the component to re-render. useRef is a synchronous hook that updates the state immediately and persists its value through the component's lifecycle, but it doesn't trigger a re-render.
You may be able to mutate it, but this will not issue a “re-render” in your component, which means the new value will not be shown in your UI.
setState in a class component, the function returned by useState does not automatically merge update objects, it replaces them. Try it here, you'll see how the id property is lost. The ... prevState part will get all of the properties of the object and the message: val part will overwrite the message property.
React will merge all state changes into one update to reduce rendering as rendering costs a lot, setState
is not surely updated when you call log. So you can just log("count="+(count+3))
or use useEffect
.
useEffect
sets a callback when state/prop change; the following snippet shows how to log count
every time it changes. You can reference the docs for more information.
const {useState,useEffect} = React;
function App() {
const [count, setCount] = useState(0);
useEffect(()=>{
console.log("effect!count=" + count)
},[count])
const onClick = () => {
setCount((prevCount) => {
console.log(prevCount + 1);
return prevCount + 1;
});
setCount((prevCount) => {
console.log(prevCount + 1);
return prevCount + 1;
});
setCount((prevCount) => {
console.log(prevCount + 1);
return prevCount + 1;
});
console.log("onclick");
};
console.log("rendering");
return <button onClick={onClick}> Increment {count} </button>;
}
ReactDOM.render(<App/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With