
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