Rohit Nandi

Full Stack Engineer

React.memo
React.memo (Published on 26th April 2025)

What React.memo Does, Precisely

High-Level Summary

React.memo is a higher-order component (HOC) for function components only.

It memoizes the result of a component render based on props.

If props are the same (by shallow equality or a custom comparator), React reuses the last rendered output.

How It's Created Internally

const MemoizedComponent = React.memo(MyComponent);

This returns:

{
  $$typeof: REACT_MEMO_TYPE,
  type: MyComponent, // original component
  compare: optionalCompareFn,
}

$$typeof is a special React symbol (Symbol.for('react.memo')).

type is your actual component function.

compare is your shallow comparison (or a custom function).

This marks the component as "memoized" — a special kind of element handled differently during rendering.

Fiber Nodes & Reconciliation

To understand memoization in React, we have to dip into React Fiber — the internal data structure React uses to track a component tree.

Each component instance is stored in a Fiber node, and React walks the Fiber tree every render.

During this traversal, React checks if a node should be reused or re-rendered.

What Happens When React Encounters a Memo Component?

Inside ReactFiberBeginWork.new.js (source of react-reconciler), you'll find this:

case MemoComponent: {
  const nextProps = thisProps;
  const prevProps = current.memoizedProps;

  let compare = Component.compare || shallowEqual;

  if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
    return bailoutOnAlreadyFinishedWork(...);
  }

  // Otherwise, render like a normal function component
}

Breakdown:

  • Get the compare function — either custom or default shallowEqual.
  • Compare prevProps and nextProps.
  • If they are equal (and the ref didn't change):
    • React bails out of rendering the component.
    • It skips diffing this subtree and reuses the previous result.
  • If not equal, React proceeds to call the component and generate new children.

shallowEqual — The Default Comparator

Here's the source-like implementation:

function shallowEqual(objA, objB) {
  if (Object.is(objA, objB)) return true;

  if (
    typeof objA !== 'object' || objA === null ||
    typeof objB !== 'object' || objB === null
  ) return false;

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);
  if (keysA.length !== keysB.length) return false;

  for (let i = 0; i < keysA.length; i++) {
    if (
      !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
      !Object.is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true;
}

This is why: If you pass an object, array, or function literal ( / [] / () => ), even if the contents are the same, React will re-render because reference changes.

React.memo in Practice — Lifecycle

Initial Render: The component renders normally.

Next Render:

  • React checks: "Is this a memoized component?"
  • Yes → compares prevProps vs nextProps
    • If equal → skip component rendering, reuse memoizedState and memoizedProps from last time.
    • If not equal → proceed to render and update the Fiber node.

The real magic is in:

  • How React bails out of work.
  • How Fiber nodes store and reuse state and props.

Bonus: Custom Implementation of React.memo

Here's a vanilla implementation that mimics the idea:

function myMemo(Component, areEqual = shallowEqual) {
  let lastProps = null;
  let lastRender = null;

  return function Memoized(props) {
    if (lastProps !== null && areEqual(lastProps, props)) {
      return lastRender;
    }

    lastProps = props;
    lastRender = Component(props);
    return lastRender;
  }
}

This is oversimplified — doesn't handle React internals like reconciliation, refs, hooks, etc., but it captures the essence.

Why React.memo is Not Always a Win

While memoization sounds like a performance win, it's not free.

Overhead:

  • Every render must now do a props comparison.
  • For lightweight components that re-render quickly, this overhead can outweigh the benefit.

When it shines:

  • Expensive child component
  • Rarely changing props
  • High-frequency re-renders of the parent

Real-world Best Practices

Scenario: Should you use React.memo?

  • Passing objects/functions inline: ❌ Not useful unless memoized too
  • Heavy child component: ✅ Yes - significant performance gain
  • List items in a large array: ✅ Yes (with proper key props)
  • Stateless UI (simple div with text): ❌ Don't bother - overhead exceeds benefit
  • Props derived from context/store: ⚠️ Only if selectors are optimized

TL;DR Mind Map of React.memo Internals

React.memo
  ↓
Creates REACT_MEMO_TYPE object
  ↓
During Reconciliation
  → Check props (shallow or custom compare)
    ↓
  [Equal]
    → Bail out, reuse fiber node
  [Not Equal]
    → Call function, update fiber, diff children