Choosing the Right Hook

Why useMemo() for Data Filtering in React

by: Cristina Rodriguez

You read in this blog about how to get familiar with useEffect() in a two-part series: here and here. But what if the problem in your project is not rendering a component after a change but filtering the data itself? As you can imagine, there is also a hook for that.

As a developer refining my skills in Next.JS, I recently encountered a challenge: I had a huge list of words to sift through, filtering out suggestions based on each keystroke the user typed. Every time the user typed a new letter, the component re-rendered, recalculating the entire list.

Learning how to solve this problem helped me to understand the difference between useEffect() and useMemo() to memoize expensive calculations, ensuring that they don't get re-executed unless their dependencies change.

My First Attempt: Using useEffect()

My initial instinct was to handle this filtering logic using useEffect(). I thought, "Why not just update my suggestions state whenever my variable userWord changes?" So I set up a useEffect() hook to monitor userWord and update the filtered suggestions accordingly.

const [userWord, setUserWord] = useState("");

const [suggestions, setSuggestions] = useState([]);

useEffect(() => {
setSuggestions(words.filter(word => partialAnagram(userWord,
word) && word.length >= 3));
}, [userWord]);

The code worked, but my app was re-rendering. Here's why: every time userWord changed, React recalculated the entire filtered list of 'suggestions.' It wasn't just about re-running the filter logic—it was about the complexity of managing one state that depended on another.

In simple words, when one state, like 'userWord,' triggers a change in another state, like suggestions, it can quickly get out of hand.

Why? Each change to userWord causes React to update suggestions, and if suggestions are stored in a state, we're doubling the workload. In situations like this, it becomes critical to find a way to only update the dependent state when absolutely necessary, to avoid the extra "cascading" updates.

The second option: Enter useMemo()

Around this time, I stumbled upon useMemo(). At first, I thought it was just another hook for specialized use cases, but then I realized it was perfect for my situation. With useMemo(), I could calculate my suggestions list once, cache the result, and only re-run the filter logic when userWord changed.

Here's how it looked when I implemented it with useMemo():

const [userWord, setUserWord] = useState('');

const suggestions = useMemo(() => words.filter(
(word) => partialAnagram(userWord, word) && word.length >= 3
), [userWord]);

What is useMemo()?

useMemo is a React Hook that optimizes performance through memoization—a technique that stores the result of an expensive function so it doesn't need to re-run on every render. When you use useMemo, React will only re-calculate the function if its dependencies change. This helps reduce unnecessary computations, making useMemo ideal for tasks like filtering data or performing complex calculations that rely on specific variables.

Why useMemo() Was the Game-Changer

Using useMemo() here made all the difference, and here's why:

  1. Focused Optimization: With useMemo(), React caches the filtered list and only recalculates it when absolutely necessary. This keeps my app from bogging down and lets me avoid unnecessary computations.
  2. Right Tool for the Right Task: I learned that useEffect() is all about handling side effects—things like API calls, event listeners, and interactions with the DOM. But this filter logic was purely a calculation, and useMemo() was built to handle that kind of task, allowing me to keep my side effects and calculations separate.
  3. A Clean Code Solution: I could finally stop juggling extra state variables and conditional checks. useMemo() helped me simplify the code, making it clear that suggestions are a memoized value that only changes with userWord.

The Learning

This experience taught me something crucial: when building React apps, performance optimizations are about using the right hook for the job. useMemo() and useEffect() might seem interchangeable at first glance, but they serve different purposes. useEffect() handles side effects; useMemo() caches calculations. Each has its place, and learning when to use them unlocked a whole new level of performance for my app.

Next time you're faced with an intensive calculation in React, take a moment to think it through. Is it a side effect? Or just a calculation? If it's the latter, useMemo() might just save you a lot of headaches—and your users a lot of lag.

useEffect vs. useMemo

Useful Reading: