I'd like to clarify my understanding of what's happening here. Any detail to improve my current understanding'd be appreciated.
function Timer() {
let [time, setTime] = useState(5);
useEffect(() => {
let timer = setInterval(() => {
setTime(time - 1);
}, 1000)
return () => clearInterval(timer);
}, );
return <div>{time}</div>
}
export default Timer
https://codesandbox.io/s/cranky-chaplygin-g1r0p
time
is being initialised to 5
.useEffect
is read. Its callback must be made ready to fire later.div
is rendered.useEffect
's callback is executed. setInterval
's callback gets ready to fire. Surely useEffect
's return
statement doesn't fire here, because if it did it would cancel the timer (and the timer does work).setInterval
's callback fires changing the state of time
(to 4).time
, a new variable, is initialised to the new time state.useEffect
is read, it's callback made ready to fire later. (This happens because there is no 2nd argument of useEffect()
).return
statement is executed. This effectively re-renders the div
.useEffect
's return
statement executes (which disables the timer
in that previous useEffect
). I'm not sure when this occurs.useEffect
's callback is executed.Your understanding of the sequence of events is correct. The only thing missing is the precise timing of the effect callbacks and cleanup.
When the component re-renders, any useEffect
s will have their dependency arrays analyzed for changes. If there has been a change, then that effect callback will run. These callbacks are guaranteed to run in the order that they're declared in the component. For example, below, a
will always be logged just before b
.
const App = () => {
const [num, setNum] = React.useState(0);
React.useEffect(() => {
setInterval(() => {
setNum(num => num + 1);
}, 1000);
}, []);
React.useEffect(() => {
console.log('a', num);
}, [num]);
React.useEffect(() => {
console.log('b', num);
}, [num]);
return num;
}
ReactDOM.render(<App />, document.querySelector('.react'));
<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 class='react'></div>
These effect callbacks will run shortly after the browser re-paints.
Now add the effect cleanup callback into the mix. These will always run synchronously just before the effect callback for a render runs. For example, let's say the component starts at Render A, and in Render A, an effect hook has returned a cleanup callback. Then, some state changes, and a transition to Render B occurs, and there exists a useEffect
with a dependency array that includes the state change. What will happen is:
You can see the source code for those last two actions here:
commitHookEffectListUnmount(Passive$1 | HasEffect, finishedWork);
commitHookEffectListMount(Passive$1 | HasEffect, finishedWork);
That first call invokes all cleanup callbacks from a prior render. That second call invokes all effect callbacks for the current render. Current render effect callbacks run synchronously after the execution of prior render cleanup callbacks.
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