Memoization

Performance optimization in React often boils down to one simple goal: Stop doing work that doesn’t need to be done.

React is fast by default, but as your application grows, you might find that components re-render more often than necessary. This is where Memoization comes in.

1. Intuition: The Forgetful Painter

Imagine you hire a painter to paint your living room.

  • Without Memoization: Every time you buy a new vase, the painter re-paints the entire room, including the walls, ceiling, and furniture, just to make sure everything matches.
  • With Memoization: You tell the painter, “Only paint the vase. The walls haven’t changed.”

In React, the “painter” is the Render Phase. If a parent component updates, React (by default) assumes everything inside it might have changed and asks all children to re-render.

[!IMPORTANT] Memoization is not free. It comes with a cost of memory usage (to store the previous result) and computation (to compare props/dependencies). Only use it when you have identified a performance bottleneck.

2. Hardware Reality: The 16ms Budget

Why do we care about re-renders?

Your browser’s main thread is responsible for running JavaScript, handling user input, and painting pixels to the screen. To achieve a smooth 60 frames per second (FPS), the browser has a budget of approximately 16.6ms per frame.

  • 10ms: React Render Phase (Calculating the diff)
  • 4ms: Browser Paint (Updating the DOM)
  • 2ms: Idle / Overhead

If your component takes 20ms to render, the browser drops a frame. The user perceives this as “lag” or “jank”. Memoization helps you stay within this 16ms budget by skipping the “Render Phase” for components that haven’t changed.

3. The Render Cascade

To understand memoization, you must first understand why React re-renders components.

  1. State or Props Change: When a component’s state or props change, React schedules a re-render.
  2. Parent Re-renders: By default, if a parent component re-renders, all of its children re-render, regardless of whether their props changed.

This “cascade” of re-renders is usually fine, but for expensive components, it can cause lag.

React Render Cascade

Parent
Child A
Standard
Grandchild
Re-renders ⚠️
Child B
Memoized
STOP
Skips Render ✅

4. React.memo

React.memo is a Higher-Order Component (HOC) that wraps a functional component. It performs a shallow comparison of the new props vs the old props. If they are the same, it skips the re-render.

const MyComponent = React.memo(function MyComponent(props) {
  /* only rerenders if props change */
});

Interactive Demo: The Re-render Flash

In this demo, observe how the “Standard List” re-renders every time you increment the parent’s count, even though its props haven’t changed. Toggle “Enable Memoization” to stop the unnecessary work.

Loading Interactive Demo...

5. useMemo

useMemo is a hook that memoizes a value. It’s useful when you have an expensive calculation that shouldn’t run on every render.

const expensiveValue = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a, b]);

When to use useMemo?

  1. Expensive Calculations: Filtering a large array, transforming data, or complex math.
  2. Referential Equality: When you need to pass an object or array to a child component wrapped in React.memo. If you create the object inline, it will be a new reference every render, breaking React.memo.

6. useCallback

useCallback is similar to useMemo, but it memoizes a function definition.

const handleClick = useCallback(() => {
  console.log('Clicked!', count);
}, [count]);

[!WARNING] Common Pitfall: Using useCallback does not prevent the function from being created. It prevents the reference from changing. The function inside is still allocated in memory every render, but React discards the new one and returns the old one if deps haven’t changed.

7. The Referential Equality Trap

This is the most common reason memoization fails. In JavaScript, two objects or functions are only equal if they share the same reference.

console.log({} === {}); // false
console.log([] === []); // false
console.log(() => {} === () => {}); // false

Why it matters in React

If you define a function or object inside your component body, it gets recreated on every render.

function Parent() {
  // ❌ This creates a NEW function reference every render
  const handleItemClick = (id) => console.log(id);

  return (
    // ❌ Child re-renders because props.onClick changed!
    // React.memo won't help here.
    <MemoizedChild onClick={handleItemClick} />
  );
}

The Fix:

function Parent() {
  // ✅ Function reference stays the same unless deps change
  const handleItemClick = useCallback((id) => console.log(id), []);

  return (
    // ✅ Child sees the same prop reference -> Skips render
    <MemoizedChild onClick={handleItemClick} />
  );
}

8. Summary Checklist

Tool Memoizes Use Case
React.memo Component Skip re-rendering a child if props haven’t changed.
useMemo Value Skip expensive calculations or stabilize object references.
useCallback Function Stabilize function references to pass to memoized children.