Reputation: 111
I'm new to React and just practicing useEffect hooks after going through a hooks course.
I have a button to toggle state change of arrowState.
export default function Test() {
const [arrowState, setArrowState] = useState(true);
const [arrowChange, setArrowChange] = useState(false);
const toggleButton = () => {
return setArrowState((arrowState) => {
return !arrowState;
});
};
return (
<>
<div>
<button
type="button"
onClick={toggleButton}
>
Toggle Arrow
</button>
</div>
{arrowChange && <Notific />}
</>
);
}
Then I have a useEffect hook which changes state of arrowChange. Also have a timeout to change state of arrowChange to false - thereby auto-disappearing the button after 2 secs.
useEffect(() => {
console.log("first line of useEffect.arrowChange is ", arrowChange);
setArrowChange(true);
console.log(
"immediately after setArrowChange to true. arrowChange is ",
arrowChange
);
setTimeout(() => {
console.log("first line of setTimeOut. arrowChange is ", arrowChange);
arrowChange && setArrowChange(false);
}, 1000);
return () => {
console.log("cleaning up arrowchange. arrowChange is ", arrowChange);
setArrowChange(false);
};
}, [arrowState]);
Based on it, I'm displaying another button with a message.
function Notific() {
return (
<button
type="button"
onClick={() => setArrowChange(false)}
>
Arrow state has been changed
</button>
);
}
As you can see, I'm logging some messages to see the effect. I'm surprised to see that even after setting arrowChange to true at the beginning of useEffect hook, it's still showing false at alternate events.
Check these console logs:
cleaning up arrowchange. arrowChange is false
Test.js:57 first line of useEffect.arrowChange is true
Test.js:59 immediately after setArrowChange to true. arrowChange is true
Test.js:65 first line of setTimeOut. arrowChange is true
Test.js:70 cleaning up arrowchange. arrowChange is true
Test.js:57 first line of useEffect.arrowChange is false
Test.js:59 immediately after setArrowChange to true. arrowChange is false
Test.js:65 first line of setTimeOut. arrowChange is false
Test.js:70 cleaning up arrowchange. arrowChange is false
Test.js:57 first line of useEffect.arrowChange is true
Test.js:59 immediately after setArrowChange to true. arrowChange is true
Test.js:65 first line of setTimeOut. arrowChange is true
Test.js:70 cleaning up arrowchange. arrowChange is true
Test.js:57 first line of useEffect.arrowChange is false
Test.js:59 immediately after setArrowChange to true. arrowChange is false
Test.js:65 first line of setTimeOut. arrowChange is false
Test.js:70 cleaning up arrowchange. arrowChange is false
Test.js:57 first line of useEffect.arrowChange is true
Test.js:59 immediately after setArrowChange to true. arrowChange is true
Test.js:65 first line of setTimeOut. arrowChange is true
Upvotes: 3
Views: 206
Reputation: 29282
To understand the output of the code, you need to understand the fundamental concepts of how react updates the state and the concept of a closure.
Every call to state setter function such as setArrowChange
is scheduled - state is not updated immediately.
As a result, logging the value of arrowChange
immediately after calling setArrowChange
will log the old value of arrowChange
.
State is constant; this means that irrespective of how many times you call the state setter function, component can't see the updated state until it re-renders.
When you create a function is javascript, it forms a closure over its surrounding scope.
In your case, the callback function of setTimeout
will log the value of arrowChange
was in-effect when the callback function was created.
If at the time of setTimeout
call, arrowChange
is false
, and before the timer expires, you update the state to true
, the callback function will still log false
because of the closure.
The cleanup
function also has a closure over the arrowChange
; it logs the value of arrowChange
that was in-effect when the cleanup function was created and returned from the callback function of the useEffect
hook.
Now let's take a look at the first group of console.log
output.
cleaning up arrowchange. arrowChange is false Test.js:57 first line of useEffect.arrowChange is true Test.js:59 immediately after setArrowChange to true. arrowChange is true Test.js:65 first line of setTimeOut. arrowChange is true
First line of output
cleaning up arrowchange. arrowChange is false
is from the cleanup function of the useEffect
hook that executes:
useEffect
againAs explained above, it logs the value of the arrowChange
which it closed over when the cleanup function of the useEffect
was created. Output of the cleanup function suggests that:
arrowChange
was false
when the cleanup function was created and returned from the useEffect
hook's callback functionarrowChange
is false
, so this output if from the cleanup function that was most likely created when the useEffect
hook executed for the first time, after the initial render of the componentNext three lines
Test.js:57 first line of useEffect.arrowChange is true
Test.js:59 immediately after setArrowChange to true. arrowChange is true
Test.js:65 first line of setTimeOut. arrowChange is true
log the latest value of arrowChange
which is most likely because of setArrowChange(true)
call in the second line of the useEffect
hook that executed after the initial render of the component.
If you observe the output of other groups of console.log
outputs, you will notice that the value of arrowChange
logged by the cleanup function is opposite to the value logged by the console.log
statements inside the callback function of the useEffect
hook.
The reason for this is that the cleanup function is from the previous execution of the useEffect
hook and it logs the value of arrowChange
that was in-effect during the previous execution of the useEffect
hook.
console.log
statements inside the callback function of the useEffect
hook log the latest value of the arrowChange
and in the next execution of the useEffect
hook, this latest value will become the previous value and the opposite of this latest value will become the latest value.
Upvotes: 4