FAQ: The Effect Hook - Control When Effects Are Called

Thank you for your response. It’s clearer now

1 Like

Thank you for these very interesting explanations!
Can you please just explain why in step 3, the existing interval continues to be cleared and the clock seems to stop, while time was updated by setTIme just before the interval was cleared?
Thank you

When we get to Step 3, our useEffect looks like:

useEffect(() => {
    const intervalId = setInterval(() => {
      setTime((prev) => prev + 1);
    }, 1000);

    return () => {

When the component is rendered for the very first time, the following happens:

  • the callback function is executed. In this particular case, the function will set up an interval through setInterval. Since we have specified the delay as 1000 milliseconds (or 1 second). the setInterval will not call the state setter setTime till one second has elapsed.

  • the cleanup function is not executed, but useEffect is made aware that if the component is re-rendered or unmounted, then the cleanup function must be executed before re-rendering/unmounting.

If the time changes or some text is typed into the input field, then the component is re-rendered. When the component is re-rendered, the following happens:

  • the cleanup function is executed. This clears the interval that was set the previous time the useEffect was called.

  • After the cleanup function has been executed, then the effect

const intervalId = setInterval(() => {...}, 1000);

is carried out.

--- Every time the useEffect is triggered for a re-render, the cleanup function is executed first and then the actual effect is run.

--- If the component is to be unmounted, then only the cleanup function is executed (the effect is not run as the component is to be unmounted).

If we change the text in the input field, useEffect is triggered. The cleanup function is run first and it clears the existing interval (regardless of whether one second has passed or not). Then a new interval is set. As we keep typing in the input field, the interval keeps getting cleared and the clock is stuck. If we stop typing for at least a second, then the clock advances by a second.

In Step 4, this is rectified by using the empty dependency array for the useEffect. The empty dependency array means that the effect (of setting an interval) will only happen the very first time the component is rendered. The cleanup function will only be executed when the component is unmounted. If the state changes (because of time being incremented OR text being changed in the input field), then the component will be re-rendered. But, because of the empty dependency array, the useEffect will not be triggered. Neither the cleanup function will be executed nor a new effect will happen in response to any re-renders. Even though the useEffect is not triggered for re-renders, the clock keeps advancing after every second (as we wanted) because the original interval set by setInterval keeps calling setTime after every second.

If I have misunderstood your question, share your thoughts and elaborate a bit more on what you find confusing.

Thank you for your answer, it is very clear!
I didn’t understand that every time the useEffect is triggered for a new rendering, the cleanup function is executed first, and only after that the actual effect is executed!
Thanks again for your answer.

1 Like

Can anyone possibly explain why we use an Effect hook with an empty dependency array to call the effect once at the beginning of a render instead of just calling a separate function once with the same ‘effect’ and just not using the effect hook altogether?

I think that, you would have no way to deal with the issue of adding an additional setInterval() on each render. If you add the setInterval() and clearInterval() outside of useEffect(), then an interval is started, and immediately stopped, and the timer never increases. If you don’t include the clear, then it keeps going up by the number of renders that have occurred, instead of just 1. useEffect() with the empty dependencies is basically saying, execute this code on the very first render, but then bypass it altogether on any subsequent ones.

Thank you for this, helped a lot !!

1 Like

I had a similar question, so I’m posting what the solution is in case someone else has this question too. It’s been over a year since the OP asked the question, so not expecting to help them.

Instead of passing in the returned values of these functions, we need to pass in the functions themselves. The anonymous function/fat arrow syntax allows us to pass a function rather that the returned value of that function.

For example, if I want to use a sum() function with setInterval and I don’t use the anonymous function syntax, this is what happens.

setInterval(sum(2, 3), 1000);

resolves to

setInterval(5, 1000);

which is not what we want. We want to pass in the function as the argument.

setInterval(() => sum(2, 3), 1000);

This one doesn’t resolve to 5; it remains as a function. The setInterval will call that function every so often.

EDIT: This was answered later in the thread but I guess it’s helpful to have this as a reply for the first time it is asked.

Question for y’all. Why are the curly braces necessary for the anonymous function in setInterval? In other words why does the lesson accept this:

useEffect(() => {
  setInterval(() => {
    setTime((prev) => prev + 1);
  }, 1000);

And not this:

useEffect(() => {
  setInterval(() => setTime((prev) => prev + 1), 1000);

It took me a while to figure out what I was doing wrong.