Code To Learn logo

Code To Learn

M5: Hooks & Performance

L4: useMemo Introduction

Optimize expensive calculations with React's useMemo hook

Learn how to prevent expensive calculations from running on every render with React's useMemo hook!

What You'll Learn

  • What useMemo is and why it matters
  • When to use useMemo
  • When NOT to use useMemo
  • Memoization concept
  • Performance optimization patterns
  • Prevent unnecessary recalculations

The Performance Problem

Every time a component re-renders, ALL its code runs again:

function HomePage() {
  const { data: listings } = useFetch('/listings');
  const [search, setSearch] = useState('');
  
  // This runs on EVERY render!
  const expensiveCalculation = () => {
    console.log('Calculating...');
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += i;
    }
    return result;
  };
  
  const value = expensiveCalculation(); // Called every render!
  
  return <div>{value}</div>;
}

Problem: When you type in the search box:

  1. search state changes
  2. Component re-renders
  3. expensiveCalculation runs again
  4. Returns same result (wasteful!)

Unnecessary calculations slow down your app, especially with complex operations like filtering large arrays, sorting data, or computing derived values.

What is useMemo?

useMemo is a React hook that memoizes (caches) the result of a calculation. It only recalculates when dependencies change.

Memoization = Remember previous result, reuse it if inputs haven't changed

Think of it like a smart calculator:

  • First time: Does the math, saves the answer
  • Next times: Checks if numbers changed
    • If same: Returns saved answer (fast! ⚡)
    • If different: Does math again, saves new answer

useMemo Syntax

const memoizedValue = useMemo(() => {
  // Expensive calculation here
  return result;
}, [dependencies]);

Parts:

  1. Function - Returns the value to memoize
  2. Dependencies - Array of values to watch
  3. Return value - The memoized result

How it works:

// First render
const result = useMemo(() => compute(a, b), [a, b]);
// Runs compute(a, b), saves result

// Second render (a and b unchanged)
const result = useMemo(() => compute(a, b), [a, b]);
// Returns saved result, doesn't run compute!

// Third render (a changed)
const result = useMemo(() => compute(a, b), [a, b]);
// Runs compute again because a changed

Simple Example

function Component() {
  const [count, setCount] = useState(0);
  const [search, setSearch] = useState('');
  
  // Runs on every render (even when search changes!)
  const doubledCount = count * 2;
  console.log('Calculated:', doubledCount);
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Doubled: {doubledCount}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <input
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />
    </div>
  );
}

Problem: Typing in search box triggers calculation, even though count didn't change!

function Component() {
  const [count, setCount] = useState(0);
  const [search, setSearch] = useState('');
  
  // Only recalculates when count changes
  const doubledCount = useMemo(() => {
    console.log('Calculated:', count * 2);
    return count * 2;
  }, [count]); // Only depends on count
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Doubled: {doubledCount}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <input
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />
    </div>
  );
}

Solution: Typing in search box doesn't trigger calculation - count unchanged!

When to Use useMemo

Use useMemo for:

When NOT to Use useMemo

Don't use useMemo for:

Premature optimization - Only use useMemo when you have a performance problem. Adding it everywhere makes code harder to read and maintain.

Avoid for:

// ❌ Simple calculations
const double = useMemo(() => count * 2, [count]);
// Just do: const double = count * 2;

// ❌ Single operations
const uppercase = useMemo(() => name.toUpperCase(), [name]);
// Just do: const uppercase = name.toUpperCase();

// ❌ Small arrays
const filtered = useMemo(() => items.filter(i => i.active), [items]);
// Only optimize if items has 1000+ elements

// ❌ Primitive values
const formatted = useMemo(() => `$${price}`, [price]);
// String formatting is already fast

Rule of thumb: Only use useMemo when:

  • Calculation takes > 5ms
  • You're processing large datasets (100+ items)
  • You've measured a performance problem

useMemo Best Practices

Always specify dependencies

// ✅ Good - all dependencies listed
const result = useMemo(() => {
  return calculate(a, b, c);
}, [a, b, c]);

// ❌ Bad - missing dependencies
const result = useMemo(() => {
  return calculate(a, b, c);
}, [a]); // b and c changes won't trigger recalculation!

Don't mutate dependencies

// ❌ Bad - mutating array
const sorted = useMemo(() => {
  return items.sort();
}, [items]); // Mutates original array!

// ✅ Good - create new array
const sorted = useMemo(() => {
  return [...items].sort();
}, [items]); // Doesn't mutate original

Keep memoized functions pure

// ❌ Bad - has side effects
const result = useMemo(() => {
  console.log('Calculating...'); // Side effect!
  saveToLocalStorage(data); // Side effect!
  return process(data);
}, [data]);

// ✅ Good - pure function
const result = useMemo(() => {
  return process(data);
}, [data]);

Use for expensive operations only

// ❌ Bad - simple operation
const double = useMemo(() => count * 2, [count]);

// ✅ Good - expensive operation
const filtered = useMemo(() => {
  return largeArray
    .filter(complexFilter)
    .sort(complexSort)
    .map(complexTransform);
}, [largeArray]);

Measuring Performance

How do you know if useMemo helps?

Use React DevTools Profiler:

  1. Open React DevTools
  2. Click "Profiler" tab
  3. Click record button
  4. Interact with your app
  5. Stop recording
  6. Review render times

Look for:

  • Components taking > 10ms
  • Frequent re-renders
  • Identical re-renders (wasted work)

Add console timing:

function Component() {
  const result = useMemo(() => {
    console.time('Expensive Calculation');
    const result = expensiveOperation();
    console.timeEnd('Expensive Calculation');
    return result;
  }, [deps]);
  
  return <div>{result}</div>;
}

Check browser console:

  • First render: Shows time (e.g., "5.2ms")
  • Later renders: No output (cached!)

Use Performance API:

function Component() {
  const result = useMemo(() => {
    const start = performance.now();
    const result = expensiveOperation();
    const end = performance.now();
    console.log(`Took ${end - start}ms`);
    return result;
  }, [deps]);
  
  return <div>{result}</div>;
}

More accurate than console.time for micro-benchmarks.

Real Example: Filtering Data

function DataTable({ data, searchTerm }) {
  // Without useMemo - runs on EVERY render
  const filteredData = data.filter(item => {
    return item.name.toLowerCase().includes(searchTerm.toLowerCase());
  });
  
  // With useMemo - only when data or searchTerm change
  const filteredData = useMemo(() => {
    return data.filter(item => {
      return item.name.toLowerCase().includes(searchTerm.toLowerCase());
    });
  }, [data, searchTerm]);
  
  return (
    <table>
      {filteredData.map(item => (
        <tr key={item.id}>
          <td>{item.name}</td>
        </tr>
      ))}
    </table>
  );
}

Performance comparison (with 10,000 items):

ScenarioWithout useMemoWith useMemo
First render15ms15ms
Re-render (same data)15ms0.1ms
Re-render (new data)15ms15ms

Benefit: 99% faster for unchanged data! ⚡

Common Mistakes

What's Next?

In Lesson 5, we'll apply useMemo to optimize our HomePage filtering logic. We'll see real performance improvements when filtering large lists! 🚀

Summary

  • ✅ useMemo memoizes expensive calculations
  • ✅ Only recalculates when dependencies change
  • ✅ Use for expensive operations on large data
  • ✅ Don't use for simple calculations
  • ✅ Always specify all dependencies
  • ✅ Measure before optimizing
  • ✅ Keep memoized functions pure

Key concept: useMemo = Smart caching for expensive calculations. Use it wisely!