BeginnerReact Concepts

useMemo in React – When Your Components Do Too Much Thinking

useMemo lets you memoize expensive calculations so they only re-run when needed — perfect for derived state or heavy logic.

By Rudraksh Laddha

Last month, I was debugging a dashboard that felt sluggish. Every keystroke in the search box triggered a cascade of recalculations across thousands of rows. The culprit? A filter function running on every single render, processing the same unchanged data over and over.

"Why is my app calculating the same result 47 times when nothing changed?"

  • Me, staring at React DevTools profiler

This is where useMemo saved the day. Instead of letting React mindlessly recompute expensive operations, I taught it to remember results and only recalculate when dependencies actually changed.

"Only recalculate when something meaningful changed, not just because React re-rendered."

What is useMemo and Why It Matters

useMemo is React's built-in memoization hook that caches expensive computation results between renders. It's essentially saying "React, remember this calculation and only redo it if the inputs actually change."

In my experience, it's most valuable for:

  • Heavy data transformations (filtering, sorting, mapping large arrays)
  • Complex calculations that depend on props or state
  • Creating stable object references to prevent unnecessary child re-renders

Think of it as React's way of being smart about when to actually do work versus when to reuse previous results.

The Syntax That Actually Makes Sense

const memoizedValue = useMemo(() => {
  // Your expensive calculation here
  return computeExpensiveValue(dependencies);
}, [dependencies]);

The pattern I follow:

  • ✅ First argument: A function that returns the computed value
  • ✅ Second argument: Array of dependencies that trigger recalculation
  • ✅ React compares dependencies using Object.is() - same as useEffect

Real Example: The Dashboard That Taught Me useMemo

const ProductDashboard = ({ products, searchTerm, sortBy }) => {
  // This was running on every render - even when just hovering buttons!
  const processedProducts = useMemo(() => {
    console.log('Processing products...'); // You'll see this less often now
    
    return products
      .filter(product => 
        product.name.toLowerCase().includes(searchTerm.toLowerCase())
      )
      .sort((a, b) => {
        if (sortBy === 'price') return a.price - b.price;
        return a.name.localeCompare(b.name);
      });
  }, [products, searchTerm, sortBy]);

  return (
    <div>
      <h2>Found {processedProducts.length} products</h2>
      {processedProducts.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
};

Before useMemo: Filter and sort ran on every render (even button hovers). After useMemo: Only runs when products, searchTerm, or sortBy actually change.

The performance difference was night and day - especially with 1000+ products.

useMemo vs useCallback: I Used to Confuse These

Hook What It Memoizes When I Use It
useMemo ✅ The result of a computation Expensive calculations, filtered data
useCallback ✅ The function itself Event handlers, callback props

Simple mental model: useMemo for values, useCallback for functions.

Production Patterns I Actually Use

  • Search/filter results - Avoid refiltering unchanged data
  • Expensive computations - Complex calculations that depend on props
  • Stable object references - Prevent unnecessary re-renders in child components
  • Data transformations - Mapping/sorting large arrays
  • Derived state - Values computed from multiple state variables

Another Real Example: Search Results That Scale

const SearchResults = ({ users, query, filters }) => {
  const filteredUsers = useMemo(() => {
    if (!query && !filters.department) return users;
    
    return users.filter(user => {
      const matchesQuery = !query || 
        user.name.toLowerCase().includes(query.toLowerCase()) ||
        user.email.toLowerCase().includes(query.toLowerCase());
      
      const matchesDepartment = !filters.department || 
        user.department === filters.department;
      
      return matchesQuery && matchesDepartment;
    });
  }, [users, query, filters.department]);

  // This stays stable unless the filtered results actually change
  const resultSummary = useMemo(() => ({
    total: filteredUsers.length,
    departments: [...new Set(filteredUsers.map(u => u.department))],
    hasResults: filteredUsers.length > 0
  }), [filteredUsers]);

  return (
    <div>
      <SearchSummary summary={resultSummary} />
      <UserList users={filteredUsers} />
    </div>
  );
};

✅ Both computations only run when their dependencies change, not on every component re-render.

❌ When I Don't Use useMemo (Hard-Learned Lessons)

Early in my React journey, I wrapped everything in useMemo. That was a mistake.

❌ I avoid it when:

  • The computation is trivial - String concatenation, simple math
  • Dependencies change on every render - You're just adding overhead
  • The component rarely re-renders - No performance gain
  • You're debugging - It can mask issues during development

Remember: useMemo has its own overhead. Use it when the benefits clearly outweigh the costs.

Battle-Tested Best Practices

Practice Why It Matters (From Experience)
Profile before optimizing Use React DevTools Profiler to find actual bottlenecks
Include all dependencies ESLint exhaustive-deps rule saves you from stale closures
Don't memoize primitives React already optimizes primitive comparisons
Pair with React.memo Child components need memo to benefit from stable props

✅ useMemo Quick Reference

Aspect Details
Purpose Cache expensive computations between renders
When it runs Only when dependencies change (shallow comparison)
Returns Memoized result of the computation function
Sweet spot Heavy data processing, stable object references
Common mistake Overusing it for simple calculations

Questions I Get Asked About useMemo

1. How do I know when to use useMemo?

Profile your app first. If you see the same expensive computation running repeatedly when inputs haven't changed, that's your cue. I use React DevTools Profiler to spot these patterns.

2. What if I forget a dependency?

Your memoized value becomes stale - it won't update when it should. Install the eslint-plugin-react-hooks exhaustive-deps rule. It'll catch these bugs for you.

3. Can useMemo prevent child component re-renders?

Indirectly, yes. If you pass a memoized object/array to a React.memo component, it won't re-render when the reference stays the same. But the child needs to be wrapped in memo too.

4. Is there a performance cost to using useMemo?

Yes, there's overhead for the dependency comparison and caching. That's why I only use it when the computation cost clearly exceeds this overhead. Simple calculations don't need it.

5. Should I memoize everything for better performance?

Definitely not. I learned this the hard way. React is already fast for most operations. Measure first, optimize second. Unnecessary memoization can actually hurt performance.

❤️ At Learn Virendana, we love creating high-quality React tutorials that simplify complex concepts and deliver a practical, real-world React learning experience for developers