Privacy Policy
Mon Dec 15
18:48

About

Blog

Github

GitHub

Github

YouTube

Github

Instagram

LinkedIn

LinkedIn

~/Users/jan/blog/optimizing-react-components-with-usememo-usecallback

back

Optimizing Expensive React Components With useMemo and useCallback (Without Overusing Them)

useMemo and useCallback optimization guide
useMemo and useCallback optimization guide

Learn when React memoization actually speeds up your UI and when it makes components slower.

When React components feel slow, developers often immediately turn to useMemo or useCallback. While these hooks can improve performance, they only work when applied strategically. Misusing them can actually reduce rendering speed. This post explores how memoization operates, when it cuts down on work, and when it simply adds unnecessary complexity.

Understanding the Problem

React re-renders components whenever state or props change. This is ordinarily quite fast. However, expensive calculations, heavy rendering, or deeply nested component trees can cause re-renders to accumulate.

Common symptoms include:

  • Repeated slow calculations every render
  • Child components re-rendering unnecessarily
  • Event handlers causing cascading re-renders
  • Laggy UI when typing or interacting with large lists

Using useMemo and useCallback appears like an obvious solution. The key issue: they only help when preventing more work than they introduce.

When useMemo Actually Helps

useMemo caches computation results, recalculating only when dependencies change.

Good use case:

import { useMemo } from "react";
export function ExpensiveList({ items }: { items: number[] }) {
// Pretend this sort costs 10–20ms
const sorted = useMemo(() => {
console.log("Sorting...");
return [...items].sort((a, b) => a - b);
}, [items]);
return <div>{sorted.join(", ")}</div>;
}

Why this works:

  • The sorting operation is expensive
  • If items didn't change, re-running the sort is wasteful
  • useMemo prevents unnecessary recalculations

When it doesn't help:

If the calculation is cheap or dependencies change every render, useMemo adds overhead without saving time.

const doubled = useMemo(() => count * 2, [count]);

This is slower than count * 2 on every render.

When useCallback Actually Helps

useCallback stores a memoized function version. It's useful when passing callbacks to child components that are memoized or optimized with React.memo.

Example:

import { useCallback } from "react";
function Parent({ items }: { items: string[] }) {
const handleClick = useCallback((value: string) => {
console.log(value);
}, []);
return items.map((i) => (
<ListItem key={i} value={i} onClick={handleClick} />
));
}
const ListItem = React.memo(function ListItem({
value,
onClick,
}: {
value: string;
onClick: (v: string) => void;
}) {
console.log("Rendered:", value);
return <button onClick={() => onClick(value)}>{value}</button>;
});

Why this works:

  • ListItem is memoized with React.memo
  • Without useCallback, the parent creates a new function on every render, causing child re-renders
  • With useCallback, children only re-render when they should

When it doesn't help:

If the child component is not memoized, then useCallback has no effect and only increases complexity.

Key Takeaways

  • useMemo and useCallback are performance tools, not default React patterns
  • Use them only when they prevent more work than they cost
  • Memoization does not magically make components "faster"
  • Profiling should always guide optimization decisions
  • Your goal is not fewer renders—it's faster renders
  • Thoughtful memoization is a powerful skill. Overuse is just noise