When you use React, one of the most popular hooks is useEffect
. A common reason we default to useEffect is because we want to update local state (useState
) or make an asynchronous call to fetch/re-fetch data. Often we will see a combination of both, but in order to re-fetch or update, we might end up including the state or the asynchronous call in the dependency array. By doing so can lead to a pain-staking problem, called INFINITE re-rendering.
What’s the problem with useEffect?
The example below shows what happens when we use useEffect
to fetch a random joke and update that result in the state, “mood”.
function Mood() {
const [mood, setMood] = useState({
myMood: "bored",
date: new Date(),
setupJoke: '',
punchlineJoke: '',
});
useEffect(() => {
const fetchData = async () => {
return await fetch('https://official-joke-api.appspot.com/random_joke', {
method: "GET",
mode: 'cors',
})
.then(response => response.json())
.then(data => {
console.log(data);
setMood({...mood, setupJoke: `${data.setup}`, punchlineJoke: `${data.punchline}`})
});
}
fetchData();
}, [mood.myMood])
return (
); } export default Mood;
If you end up trying this example out, you’ll notice that the random joke, rendering on the page, is constantly re-fetching to the point the page will break. Even if you remove mood
from the dependency array, you will immediately see a yellow squiggly line in your IDE, suggesting:
“React Hook useEffect has a missing dependency: ‘mood’. Either include it or remove the dependency array. You can also do a functional update ‘setMood(m => …)’ if you only need ‘mood’ in the ‘setMood’ call.”
Naturally, you might end up reinserting mood
and setMood
, which forces you to return to the original problem of this infinite loop.
Not to mention, this suggestion is telling you to include a whole object, which is another problem. And now the compiler isn’t looking for updates to mood.setupJoke
or mood.punchlineJoke
(where the fetched data was updated into), but observing changes to the whole object mood
. So whenever, mood.myMood
or mood.date
changes, then useEffect
is called every time along with every second the data is re-fetched. To attempt to fix this problem, you would also try to put [mood.setupJoke, mood.punchlineJoke]
in the dependency array, but this will ALSO cause an infinite loop.
What can we do?
Thankfully we have two hooks that can be better solutions to our problem. useCallback
and useMemo
.
`useCallback`
This hook caches the return value until one of the dependencies in the dependency array changes. Once the dependency changes, the return type of the function is re-evaluated.
`useMemo`
On the other hand, this hook caches the function. When, again, the dependency in the dependency array changes, the function is also re-evaluated.
What’s a potential solution and why?
useMemo(() => {
const fetchData = async () => {
return await fetch('https://official-joke-api.appspot.com/random_joke', {
method: "GET",
mode: 'cors',
})
.then(response => response.json())
.then(data => {
console.log(data);
setMood({...mood, joke: `${data.setup} --- ${data.punchline}`})
});
}
fetchData();
},[mood.myMood])
There are two things to notice here: useMemo
and mood.myMood
I chose useMemo
because it caches the return value of fetchData()
. Only when mood.myMood
updates, will this hook re-fetch the data to cache a new value. Because mood.myMood
is a different property than the ones updated when the data is fetched, mood.setupJoke
and mood.punchlineJoke
remain cached with the previously fetched data. Finally, we’ve avoided infinite re-rendering! 🎉
So now you know. I hope you’ll think twice before using useEffect
. Good luck!