FAQ: The Effect Hook - Control When Effects Are Called

Step 2:
I’m not sure I understand:
1/ useEffect() is called when the component mount.
2/ The interval is created.
When exactly the interval is cleaned?
It can’t be returned directly or the time would not update. So the returned function is called when the component unmount, or before it will be re-rendered?
So, when it update, it create a new interval every second, cleaning the previous one?

Hello, I dont understand the function shorthand syntax! I don’t understand how my code is different. Can somebody explain?

My code:

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

The answer:

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

useEffect accepts 2 arguments: a callback function and an optional array of dependencies. Your setInterval callback function needs to be within the parentheses after useEffect.

tbh, I’m not sure why the arrow functions are needed before setInterval and setTime, which are already functions. Anyone care to explain?

yeah, I have the exact same question,
why do we need to wrap setTime in an anonymous function when it is already a function:

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

It doesnt work, but for the life of me cannot understand why

Looks like you’re missing the ending }).

why would the commented code run but not the uncomented code? edit: I see someone has asked the but even with mine having the missing closings it still does not run.

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

I would say it is probably something to do with the way setInterval handles function calls?

In the commented code setInterval is passed an anonymous function, which then calls setTime, passing another anonymous function.

Perhaps it would work if written like so:

const callSetTime = () => {
    setTime(prev => prev + 1);
};

useEffect(() => {
    setInterval(callSetTime(), 1000);
});

Please feel free to correct me if this isn’t the case.

1 Like

Thanks for the response. Yeah, Im pretty sure its because in my example I am passing the returned value of the function instead of passing set interval a function. I think if we did this (even yours is calling callSetTime() )

useEffect(() => {
    setInterval(callSetTime, 1000);
});

So thats why they are passing an anonymous arrow function. yours works but you have to get ride of the () call.
Thanks!

I think you’re exactly right. This conversation has helped me understand useEffect even more. I feel like I did exactly what you did originally, and didn’t appreciate the nuance for the callback function vs returned value of a function. Thanks!

Based on the other conversation, I believe we’ve come to the conclusion and reason is as follows. Taking the question as to why we need the arrow functions are needed before setTime, this is because setTime is being passed as the first argument to setInterval. setTime is a function that was returned from useEffect that takes a parameter to update the state we created (i.e., it’s input is not required to be a FUNCTION itself).

So following that logic. If we look at the setInterval MDN docs, it expects a FUNCTION as a first argument. So the nuance that I believe I got right now is that setTime is a function, but setTime(arg) is not. setTime(arg) will return a parameter of undefined that is of the type undefined. But if you console.log(typeof setTime) it would log “function” and not “undefined”. So by adding the parenthesis after the function, you are no longer passing a function as an argument to the function that is EXPECTING a function (you are passing undefined). Hence, we instead wrap it in an arrow function so we can return the result of that function as part of another function and still be passing in a function instead of a value of undefined.

Hopefully that makes sense?

2 Likes

Yeah that makes sense, probs should’ve tested it before I posted the code :sweat_smile:

mtrtmk,

I have read your explanations in this thread and a few others. They are excellent and very enlightening. I alway copy and paste them as comments below the code in my local copy of the project. Thank you for your contributions.

1 Like

mtrtmk,

re: your response to: [quote=“text9498111390, post:11, topic:535826”]
...useEffect() as the second argument.”
I am wondering how is the “time” state updating every second even after the first render if we have passed an empty array to useEffect() …
[/quote]

I understand your explanation somewhat. I verified it by console.log statement at the top of the useEffect function. If you omit the empty array, you will see multiple logs. If you include the empty array, you will see only one log. But time is a state variable, and it changes every second (setTime((prev) => prev + 1);). When state changes, React renders. That is the issue confounding me.

I don’t quite follow the issue. Perhaps you can flesh out your argument a bit more. I may be misunderstanding your point of confusion.

If you click the folder icon and click on the index.js file, you can see the call to render the <Timer /> component. This mounts the component (renders it the first time). In Timer.js, you can see what exactly gets rendered (in our case an h1 element and an input element). The useEffect kicks in and sets an interval via the setInterval function. Within the setInterval, we are indeed changing the state variable time via its state setter setTime. When a second (1000 ms) passes, setInterval calls setTime. This changes time and causes the component to render again. However, this is a re-rendering. The component is mounted only when the very first render happens. Re-renderings don’t mount the component again. The mounting only happens the first time. Since we have passed an empty dependency array [] to the useEffect, so the interval is set only at the first render. Subsequent renders don’t bring the useEffect into play again because of the empty dependency array. When the component unmounts, then the cleanup function clearInterval is called.

As I said, I am most likely not understanding your point of confusion. If you can elaborate a bit more, perhaps the issue will become more clear.

1 Like

My experience with React is very limited, and I had a lapse in memory. This part of your answer refreshed it.

" When a second (1000 ms) passes, setInterval calls setTime . This changes time and causes the component to render again. However, this is a re-rendering. The component is mounted only when the very first render happens. Re-renderings don’t mount the component again. The mounting only happens the first time."

Your description above triggered my memory of the lesson or article on React’s virtual DOM whose purpose is to track what changed so that React only updates the actual DOM with those changes. So, in this case, only the time and possibly notes are changing so only those are things re-rendered. I hope that is correct.

Thanks for the help.

1 Like

Hi!

I noticed something interesting while doing this exercice :
in the previous exercises, we always put a callback function as an argument of the state setter function, but it doesn’t always have to be.

This code works:

const handleChange = ({target}) => {
const newName = target.value;
setName( () => newName );
}

But this code works as well:

const handleChange = ({target}) => {
const newName = target.value;
setName( newName )
}

useEffect(() => {
    const intervalId = setInterval(() => {
      setTime((prev) => prev + 1);
    }, 1000);
    // return () => {
    //   clearInterval(intervalId);
    // };
  }, []);

I feel confused too. If I just need a timer tick tock forever, I think I don’t have to clear interval?

1 Like

I’m finding the React content to be incredibly obtuse. There’s about 5 steps packed into each one and by the time I got to step 4 I’d forgotten what the lesson was supposed to be about- cleaning up with empty arrays.
For a much MUCH better explanation of useEffect check this video

4 Likes

Please I need help understanding this exercise. How does setTime() affect setName() when setName() was not called in useEffect?

In the exercise, we have a function component Timer. This component has a HTML element h1. We have also used useState to manage the state of time. Whenever time changes, it changes the state of the Timer component causing it to be re-rendered. When the Timer component is rendered for the first time, it is mounted. Thereafter, it is re-rendered every time its state changes.

Every time the component is rendered, the useEffect calls the callback function passed to it as an argument. Doing so after every render is the default behavior of the useEffect hook.

In Step 1 of the exercise, we create a useEffect which makes a call to setTime after every second. Recall that the default behavior of useEffect is to call the callback function after every render. When the Timer component is rendered initially, it is mounted and the useEffect creates an interval which calls setTime after every second. After the 1st second has passed, time changes and therefore state of the Timer component changes. Consequently, the component is re-rendered (not re-mounted, just rendered again). The re-render causes the useEffect to respond. It creates another interval which calls setTime after every second. The problem is that we keep stacking intervals without clearing them. Initially there was just one interval. After 1 second, there are two intervals. After 2 seconds, there will be four intervals. After 3 seconds, there will be eight intervals (because after every second, all existing intervals update time causing the component to re-render after every update. This in turn causes useEffect to respond and consequently more and more intervals are created). If you look at the time, you will see the seconds jumping in the sequence 1, 3, 7, 15, 31, ...

In Step 2, we try to fix the above behavior by adding a cleanup function to the useEffect which clears the interval. When the component is mounted, an interval is created. After 1 sec, time changes causing the component to re-render. Before the component is re-rendered, the cleanup function clears the existing interval. Then, a new interval is created. After another second, time is updated, component is re-rendered. Before the re-render, the interval is cleared and then a new interval is created. And so on.

In Step 3, we add a HTML text input element to the Timer component. We use the useState hook to manage the state of name. We also add an event listener to the input element, so that every time something changes in the input field, the handleChange event handler will be called. The handleChange function uses setName to update the state of name.

At this point, the Timer component’s state depends on time and name (as opposed to Step 1 when the state only depended on time). Whenever time is updated or the text in the input field is changed, the state of the component changes causing it to be re-rendered.

The default behavior of useEffect is to respond to every re-render, regardless of whether the re-render was triggered by a change in state of time or by a change in state of name.

This means that whenever we type in the text input field, the state of name changes. This causes the component to re-render which in turn causes the useEffect to respond. The cleanup function clears the existing interval (regardless of whether 1 second has elapsed). A new interval is set. As we keep typing, component keeps re-rendering, the useEffect keeps responding, the existing interval keeps getting cleared and the clock seems to pause until one second has elapsed after we stop typing.

In Step 4, the above is fixed by passing an empty dependency array as the second optional argument to the useEffect. This allows us to change the default behavior of useEffect. Instead of useEffect responding after every re-render, the empty dependency array modifies the response so that the callback function in useEffect is only called after the very first rendering (when component is mounted) and the cleanup function is only called once after the final rendering of the component.

The final behavior is: The component is rendered (mounted). useEffect creates an interval (with setInterval making a call to setTime after every second). When time is updated (a second elapses) OR name is updated (handleChange changes the state of name), the state of the Timer component changes. This causes the component to re-render. The useEffect is supposed to respond to re-renders. But because we passed an empty dependency array, this particular useEffect does not respond to the re-renders. The clock keeps running because of the original setInterval which keeps calling setTime after every second. When the component is unmounted, then after the final rendering, the useEffect employs the cleanup function to clear the interval.

3 Likes