I know this seems as unusual example, but still I can't seem to explain precisely why do I never see valueB
printed on console after I click the div
?
Note that since I am calling the two set state calls in a setTimeout
, they are not batched.
function App() {
let [a, setA] = React.useState();
let [b, setB] = React.useState();
React.useEffect(() => {
console.log('Entering useEffect', a, b);
return () => {
console.log('Entering cleanup', a, b);
setA(null);
setB(null);
};
}, [a, b]);
console.log('Render', a, b);
return (
<div
onClick={() => {
setTimeout(() => {
setA('valueA');
setB('valueB');
}, 100);
}}
>
<h1>Test App</h1>
</div>
);
}
ReactDOM.render(
<App/>,
document.getElementById("react")
);
<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="react"></div>
Normally useEffect
and its cleanup are called asynchronously (on another stack) after render. But if you call setState
in a timer and there are useEffect
callbacks, they are invoked eagerly but all accumulated state change from cleanup is called after setState
that invoked this operation.
So in your example when setTimeout
handler is called:
setA
: a
state is changed and two state changes from cleanup are added to pending state change queuesetB
: first valueB
is applied to b
and then null
from the cleanup (it's kind of batched here)This tries to imitate the behaviour when state updates are actually batched like in click handlers (first apply state updates from the click handler and then from useEffect
).
You can see what's happening more clearly when you use updater functions:
function App() {
Promise.resolve().then(() => console.log("**** another stack ****"));
console.log("before useStateA");
let [a, setA] = React.useState();
console.log("between useStates");
let [b, setB] = React.useState();
console.log("after useStateB");
React.useEffect(() => {
console.log('Entering useEffect', a, b);
return () => {
console.log('Entering cleanup', a, b);
setA(() => (console.log("setting a to null from cleanup"), null));
setB(() => (console.log("setting b to null from cleanup"), null));
};
}, [a, b]);
console.log('Render', a, b);
return (
<div
onClick={() => {
setTimeout(() => {
console.log("****timer start****");
setA(() => (console.log("setting a to valueA from timer"), "valueA"));
console.log("between timer setters");
setB(() => (console.log("setting b to valueB from timer"), "valueB"));
console.log("****timer end****");
}, 100);
}}
>
<h1>Test App</h1>
</div>
);
}
ReactDOM.render(
<App/>,
document.getElementById("react")
);
<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="react"></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