6 min read
0%

Memoization: React.memo, useMemo, useCallback

Back to Blog
Memoization: React.memo, useMemo, useCallback

Memoization: React.memo, useMemo, useCallback

React’s memoization tools let you skip re-renders and recomputations when inputs haven’t changed. Use them deliberately — they add complexity and aren’t free.

React.memo

Wraps a component and skips re-rendering if props haven’t changed (shallow comparison):

const UserCard = React.memo(function UserCard({ user, onEdit }) {
  return (
    <div>
      <span>{user.name}</span>
      <button onClick={onEdit}>Edit</button>
    </div>
  );
});

UserCard won’t re-render when its parent re-renders, as long as user and onEdit are the same references.

The catch: if the parent creates onEdit inline, it’s a new function reference every render — memo does nothing:

// Breaks memo: new function reference on every parent render
<UserCard onEdit={() => handleEdit(user.id)} />

Fix with useCallback.

useCallback

Memoizes a function reference across renders:

const handleEdit = useCallback(() => {
  editUser(user.id);
}, [user.id]);

<UserCard user={user} onEdit={handleEdit} />

handleEdit keeps the same reference as long as user.id doesn’t change. Now React.memo can bail out successfully.

Only memoize callbacks when:

  • The function is passed to a memoized child (React.memo)
  • The function is a dependency in useEffect or useMemo

useMemo

Memoizes a computed value:

const sortedItems = useMemo(() => {
  return items.slice().sort((a, b) => a.date - b.date);
}, [items]);

Recomputes only when items changes. Useful when the computation is expensive and the component re-renders frequently for unrelated reasons.

Measuring “expensive”: wrap in console.time. If the computation takes under ~1ms, useMemo overhead may outweigh the savings.

Custom Equality in React.memo

The second argument defines custom comparison logic:

const Chart = React.memo(
  function Chart({ data, config }) { ... },
  (prevProps, nextProps) => {
    return prevProps.data.id === nextProps.data.id; // ignore config changes
  }
);

Return true to skip re-render, false to re-render.

Referential Stability

The whole system depends on stable references. Objects and arrays are new references every render unless memoized:

// Bad: new object every render — breaks memo downstream
<Chart config={{ color: 'blue', width: 2 }} />

// Good: stable reference
const config = useMemo(() => ({ color: 'blue', width: 2 }), []);
<Chart config={config} />

When to Add Memoization

Profile first. Add memoization when:

  • A component re-renders visibly too often (check React DevTools Profiler)
  • A computation takes >1ms and runs on every render
  • A callback is passed to a memoized child that would otherwise re-render

Don’t add it preemptively — it makes code harder to read and can mask real performance issues.


Canvas is not supported in your browser