A Look Behind the Scenes of React & Optimization TechniquesEntry 5 of 10
useMemo (and How It Differs from useCallback)
useMemo(calculate, deps) caches the result of a calculation and recomputes it only when a dependency changes.
Notes
- Use it for expensive calculations (sorting or filtering large lists, heavy math), or to keep an object or array identity stable for a
memo()child. - Don't wrap cheap calculations. The dependency comparison has its own cost.
- Dependencies follow the same rules as
useEffect. - Never mutate props or state inside it.
Array.prototype.sort()sorts in place, soplayers.sort(...)mutates the prop. Copy first ([...players].sort(...)) or usetoSorted()(ES2023).
useCallback vs useMemo:
| Aspect | useCallback | useMemo |
|---|---|---|
| Caches | A function | A value |
| Returns | The same function instance | The same computed result |
| Typical use | Stable callbacks for effects or memo() children | Expensive derived data |
| Relationship | useCallback(fn, deps) | useMemo(() => fn, deps) (equivalent) |
What / Why / When
- What
A cached derived value.
- Why
Skips repeated expensive work on re-renders that don't affect the result.
- When
The profiler shows a slow calculation in render, or a
memo()child receives a derived object/array.- Use case
Checking whether a number is prime, or sorting a player list.
Code compare
import { useMemo } from "react";type PlayerData = { id: string; name: string };export function PlayerList({ players }: { players: PlayerData[] }) { const sortedPlayers = useMemo( () => [...players].sort((a, b) => a.name.localeCompare(b.name)), // copy, don't mutate props [players], ); return ( <ul> {sortedPlayers.map((p) => ( <li key={p.id}>{p.name}</li> ))} </ul> );}// Inside Counter: recompute only when initialCount changesconst initialCountIsPrime = useMemo(() => isPrime(initialCount), [initialCount]);The closest equivalent is computed(), which caches its result and recomputes only when a signal it reads changes:
import { Component, computed, input } from "@angular/core";type PlayerData = { id: string; name: string };@Component({ selector: "app-player-list", template: ` <ul> @for (p of sortedPlayers(); track p.id) { <li>{{ p.name }}</li> } </ul> `,})export class PlayerListComponent { players = input.required<PlayerData[]>(); sortedPlayers = computed(() => [...this.players()].sort((a, b) => a.name.localeCompare(b.name)));}A pure pipe is the template-level alternative. Pipes are pure by default, so pure: true can be omitted:
import { Pipe, PipeTransform } from "@angular/core";@Pipe({ name: "sortedPlayers" })export class SortedPlayersPipe implements PipeTransform { transform(players: PlayerData[]): PlayerData[] { return [...players].sort((a, b) => a.name.localeCompare(b.name)); }}@for (p of players() | sortedPlayers; track p.id) {<li>{{ p.name }}</li>}Differences and gotchas
- Is
useMemolike a pipe? Like a pure pipe, yes: both skip recalculation when the input reference is unchanged. Butcomputed()is the closer match, because it lives in the class, can be reused in code, and tracks dependencies automatically. useMemoneeds a hand-written dependency array.computed()tracks the signals it reads.- Pure pipes suit reusable formatting shared across templates.
computed()suits component-specific derived data.
Quick reference
| React | Angular |
|---|---|
useMemo(() => calc, [deps]) | computed(() => calc) |
| — | Pure pipe (template-level) |
useCallback(fn, [deps]) | Class method (always stable) |