Yo, it’s your boy gabe back with another edition of “Bad ways you’re probably using React.” Today, we’re going to talk about a common mistake that developers make (and I made, up until last year) when using React: not using the useMemo hook.
As Kanye West once said, “I hate when I’m on a flight and I wake up with a water bottle next to me like oh great now I gotta be responsible for this water bottle.” Just as he dislikes being responsible for something he didn’t need, React developers don’t want to be responsible for re-rendering components unnecessarily.
React components can re-render frequently, even if nothing has changed, leading to a decrease in performance. This issue can be solved by using the useMemo
hook, which allows you to memoize expensive calculations so that they are only recomputed when necessary.
But what is the useMemo
Hook?
Basically, useMemo
is a way to optimize your React code by memoizing expensive calculations. It takes two arguments: a function that does the calculation, and an array of dependencies. If any of the dependencies change, the function gets re-run. But if they don’t, the memoized value is returned instead.
So, what does that mean in plain English? Let’s say you have a component that displays a list of songs. Each time the component renders, it has to loop through the list and calculate the total length of all the songs. That’s a lot of work, especially if the list is long. But with useMemo, you can memoize that calculation so that it only runs when the list changes. That way, your UI stays fast and responsive.
An Example
Let’s take the song length example above. Without useMemo
your code would probably look like
import React, {useEffect, useState} from "react"; const SongList = ({songs}) => { const [totalLength, setTotalLength] = useState(0); const calculateTotalLength = () => { let length = 0; songs.forEach(song => { length += song.length; }); return length; }; useEffect(() => { const length = calculateTotalLength(); setTotalLength(length); }, [songs]); return ( <div> <h2>Total Length: {totalLength}</h2> <ul> {songs.map(song => ( <li key={song.id}> {song.title} ({song.length}) </li> ))} </ul> </div> ); };
While this code is not inherently bad, there are some considerations to keep in mind:
- The
calculateTotalLength
function is called every time the component renders, even if thesongs
prop has not changed. This can be inefficient if the list of songs is long and the calculation is complex. - The
useEffect
hook updates thetotalLength
state every time thesongs
prop changes. While this is necessary for the component to display the correct total length, it can also cause unnecessary re-renders if other parts of the component are also dependent on thetotalLength
state.
A better approach using useMemo
import React, {useEffect, useMemo, useState} from "react"; const SongList = ({songs}) => { const [totalLength, setTotalLength] = useState(0); const memoizedTotalLength = useMemo(() => { let length = 0; songs.forEach(song => { length += song.length; }); return length; }, [songs]); useEffect(() => { setTotalLength(memoizedTotalLength); }, [memoizedTotalLength]); return ( <div> <h2>Total Length: {totalLength}</h2> <ul> {songs.map(song => ( <li key={song.id}> {song.title} ({song.length}) </li> ))} </ul> </div> ); };
As you can see, the main difference here is the use of the useMemo hook to memoize the calculation of the total length. The calculation only runs when the songs prop changes, which helps to improve performance by avoiding unnecessary re-renders. The memoized value is then passed down to the useEffect hook to update the state of totalLength only when the memoized value changes.