React useMemo and useCallback

Optimize component performance by memoizing expensive calculations with useMemo and stable function references with useCallback.

TL;DR

  1. Cache heavy computation results across render cycles using the useMemo hook.
  2. Preserve stable function identity across re-renders with the useCallback hook.
  3. Prevent downstream component re-renders by pairing memoized callbacks with memo.

Caching Values with useMemo

    Expensive Filter and Sort

    Cache filtered array computations until source data changes.

    const visibleItems = useMemo(() => {
      return items
        .filter((i) => i.price <= maxPrice)
        .sort((a, b) => a.price - b.price);
    }, [items, maxPrice]);
    Stable Object Reference

    Prevent child re-renders caused by new object literals.

    const options = useMemo(() => {
      return { theme: isDark ? 'dark' : 'light' };
    }, [isDark]);
    Heavy Mathematical Calculation

    Memoize complex statistical transforms or graph metrics.

    const stats = useMemo(() => {
      return computeComplexStats(dataset);
    }, [dataset]);

Stable Functions with useCallback

    Child Action Handler

    Keep callback reference stable to prevent memoized child re-renders.

    const handleDelete = useCallback(
      (id: string) => {
        setItems((prev) =>
          prev.filter((it) => it.id !== id)
        );
      },
      []
    );
    Effect Dependency Callback

    Prevent infinite effect re-runs by memoizing handler prop.

    const fetchData = useCallback(async () => {
      const res = await api.get(url);
      setData(res);
    }, [url]);
    Debounced Callback

    Stabilize debounced functions across component renders.

    const debouncedSearch = useCallback(
      debounce((q: string) => search(q), 300),
      []
    );

Pairing with React.memo

    Memoized Child Component

    Skip re-rendering child when incoming props remain unchanged.

    export const ItemRow = memo(function ItemRow({
      item,
      onSelect,
    }: ItemRowProps) {
      return (
        <RowView title={item.name} onClick={onSelect} />
      );
    });
    Custom Comparison Function

    Supply custom comparison predicate for deep prop checks.

    export const Card = memo(CardView, (prev, next) => {
      return prev.item.id === next.item.id;
    });
    Children Invalidation Notice

    Passing inline JSX as children always busts React.memo.

    // WARNING: inline children breaks memo
    <MemoChild><Text>Dynamic</Text></MemoChild>

Optimization Pitfalls

    Premature Optimization

    Adding hooks to basic math costs more than it saves.

    // AVOID: unnecessary useMemo
    const total = useMemo(() => a + b, [a, b]);
    Missing Dependencies

    Omitting variables captures stale closure values.

    // BAD: filterValue omitted from deps
    const filter = useCallback(() => {
      return query(val);
    }, []);
    React Compiler Future

    Modern toolchains automate memoization during build.

    // React Compiler automatically memoizes
    const list = items.filter(fn);

Tips

  1. Profile before memoizing; avoid wrapping cheap operations in useMemo because hook overhead exceeds calculation cost.
  2. Pass callbacks created with useCallback into child components optimized with React.memo to prevent unwanted re-renders.

Warnings

  1. Omitting reactive values from the dependency array produces stale calculations and broken event handlers in useCallback.
  2. Do not rely on useMemo as a semantic guarantee; React may release cached values to free memory under high resource pressure.

In Practice

FAQ