You mentioned best practice so I'll just cover what occurs to me. There's actually a lot of cool things at play here.
As others have mentioned useEffect runs after every render, including the first. It's effectively both componentDidMount and componentDidUpdate.
Render is running twice because the useEffect is changing react state after the first render.
- First Render -> Sets up effect
- Effect triggers immediately afterwards -> changes
timeOfDay - React runs render again to deal with the new state of
timeOfDay
The simplest fix would be to set the first timeOfDay as a default during the first render. This way it won't have to change during the effect.
const [date, setDate] = useState(new Date())
const [timeOfDay, setTimeOfDay] = useState(getTimeOfDay(date))
Then move the code to set timeOfDay inside the interval with setDate.
You can actually do better, though, because just by putting the time of day in react state you're breaking the minimal essential state principle, which is fairly important in declarative programming. (note: I just made up that term. I don't think there's a good term for it yet.) What I mean by this is that timeOfDay is entirely derived from date, which is already in react state, so there is no reason (besides performance issues) to have both held in react state. Anytime this happens you should consider eliminating the redundant state. In this case this would involve deleting the timeOfDay state and just writing
<>
Good {getTimeOfDay(date)}, Name
</>
As far as I can tell, the only reason to ever hold onto timeOfDay as state is if your getTimeOfDay were expensive and if rendering happened more often than once a second, but even in that case you would just want to use reacts memoization. That would look like
const timeOfDay = useMemo(() => getTimeOfDay(date), [date]);
Looks similar, right? It's basically the same, but it only runs during the render, and therefore can't cause the double render issue you're having. Specifically, useMemo will run getTimeOfDay during the first render, but then will only rerun getTimeOfDay on any subsequent render if [date] has changed; otherwise it will return the previous timeOfDay that was used.
One last thing worth mentioning. You pass [date] into your useEffect, but this is actually wrong. It will work, but it's not the intended use case. That array is used to tell react what state the effect function is reading from to cause its side-effects, but you're not reading from date, so you don't need to rerun the effect when date changes. What you really want is to pass in []—no state. That way react knows that you side-effect doesn't depend on anything, and should only run once during the first render. Right now your use of an interval rather than a timeout is kind-of pointless, as react is canceling and reestablishing the interval on every render.
With all the changes I've described so far your component would look like.
const PersonalGreeting = (): ReactElement => {
const [date, setDate] = useState(new Date())
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date())
}, 1000)
return () => {
clearInterval(timer)
}
}, [])
return (
`Good ${getTimeOfDay(date)}, Name`
)
}
I also switched to string templating, which I think is nicer, but that's more of an opinion.
Answer from R Esmond on Stack OverflowUnderstanding React hook useEffect & best practice
UseEffect Best Practices
reactjs - What is the good practice to use React.JS useEffect hook for multiple states - Stack Overflow
what do you actually use useEffect for?
Videos
You mentioned best practice so I'll just cover what occurs to me. There's actually a lot of cool things at play here.
As others have mentioned useEffect runs after every render, including the first. It's effectively both componentDidMount and componentDidUpdate.
Render is running twice because the useEffect is changing react state after the first render.
- First Render -> Sets up effect
- Effect triggers immediately afterwards -> changes
timeOfDay - React runs render again to deal with the new state of
timeOfDay
The simplest fix would be to set the first timeOfDay as a default during the first render. This way it won't have to change during the effect.
const [date, setDate] = useState(new Date())
const [timeOfDay, setTimeOfDay] = useState(getTimeOfDay(date))
Then move the code to set timeOfDay inside the interval with setDate.
You can actually do better, though, because just by putting the time of day in react state you're breaking the minimal essential state principle, which is fairly important in declarative programming. (note: I just made up that term. I don't think there's a good term for it yet.) What I mean by this is that timeOfDay is entirely derived from date, which is already in react state, so there is no reason (besides performance issues) to have both held in react state. Anytime this happens you should consider eliminating the redundant state. In this case this would involve deleting the timeOfDay state and just writing
<>
Good {getTimeOfDay(date)}, Name
</>
As far as I can tell, the only reason to ever hold onto timeOfDay as state is if your getTimeOfDay were expensive and if rendering happened more often than once a second, but even in that case you would just want to use reacts memoization. That would look like
const timeOfDay = useMemo(() => getTimeOfDay(date), [date]);
Looks similar, right? It's basically the same, but it only runs during the render, and therefore can't cause the double render issue you're having. Specifically, useMemo will run getTimeOfDay during the first render, but then will only rerun getTimeOfDay on any subsequent render if [date] has changed; otherwise it will return the previous timeOfDay that was used.
One last thing worth mentioning. You pass [date] into your useEffect, but this is actually wrong. It will work, but it's not the intended use case. That array is used to tell react what state the effect function is reading from to cause its side-effects, but you're not reading from date, so you don't need to rerun the effect when date changes. What you really want is to pass in []—no state. That way react knows that you side-effect doesn't depend on anything, and should only run once during the first render. Right now your use of an interval rather than a timeout is kind-of pointless, as react is canceling and reestablishing the interval on every render.
With all the changes I've described so far your component would look like.
const PersonalGreeting = (): ReactElement => {
const [date, setDate] = useState(new Date())
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date())
}, 1000)
return () => {
clearInterval(timer)
}
}, [])
return (
`Good ${getTimeOfDay(date)}, Name`
)
}
I also switched to string templating, which I think is nicer, but that's more of an opinion.
Final version:
const PersonalGreeting = (): ReactElement => {
const [timeOfDay, setTimeOfDay] = useState(getTimeOfDay(new Date()))
useEffect(() => {
const timer = setInterval(() => {
setTimeOfDay(getTimeOfDay(new Date()))
}, 60000)
return () => {
clearInterval(timer)
}
}, [])
return useMemo(() => {
return (
<PersonalGreetingContainer>
<PersonalGreetingHeading weight="light">
Good {timeOfDay},{' '}
{givenName}
</PersonalGreetingHeading>
</PersonalGreetingContainer>
)
}, [givenName, timeOfDay])
}
It depends on the situation, but for most cases you should use separate useEffect for each use case.
If you do:
useEffect(() => {
if ( first ) {
// do the stuff for first state changes
}
if ( second ) {
// do the stuff for second state changes
}
}, [first, second])
This useEffect will run every time first and second changes. You should ask yourself, do I need to check first and update it when second changes, and vice versa. If that is the case, you should combine them into a single useEffect, although I would personally not use separate state variables for that.
In the second case, you isolate the changes.
useEffect(() => {
// do the stuff for first state changes
}, [first])
useEffect(() => {
// do the stuff for second state changes
}, [second])
When first changes, you are going to handle the state changes for first and the same thing for second. To avoid any side effects, you should try to separate the useEffect calls.
I would vote for the 2nd option for a couple of reasons:
- Reduce unnecessary checks when combining multiple state changes
- The First option always get trigger every state/props changes
- Taking more control when to run which useEffect.
- Separate of concern
And React actually encourage us to do that.
But it entirely depends on your case
Now that the new React doc is labelling useEffect as an escape hatch (along with useRef), I'm wondering what do you guys actually use useEffect for in your apps, not conceptually what you can use it for, but what you actually use it for. For instance, if you're using swr or react query for fetching, you don't actually have to import/use useEffect yourself for fetching anymore, although you can still do that conceptually.
It would be nice if you can also talk about something that you used to use useEffect to do, but switched to a new library/pattern as replacement.