Accent

πŸ‘‹ Download my CV
Download CV
All Articles
Optimizing React Renders: Patterns I Actually Use
Performance Nov 2024 Β· 5 min read

Optimizing React Renders: Patterns I Actually Use

Real-world patterns from my projects β€” memo, lazy loading, virtualization, and when NOT to optimize.

The Golden Rule: Profile First

Before touching memo or useMemo, open React DevTools Profiler and confirm you have an actual problem. Most apps never need manual memoization β€” React’s reconciler is already very fast.

That said, here are the patterns I reach for when profiling reveals genuine pain.

1. React.memo for Pure Components

Wrap components whose props rarely change but that live inside frequently re-rendering parents:

const ProjectCard = React.memo(({ project }: { project: Project }) => (
  <div className="card">
    <h3>{project.title}</h3>
    <p>{project.description}</p>
  </div>
));

The rule: memo pays off when the component’s render cost > comparison cost. Don’t wrap <div> wrappers β€” wrap heavy components with expensive children or DOM.

2. useMemo for Expensive Derivations

// Without memo: sorted on every render, even on unrelated state changes
const sorted = projects.sort((a, b) => b.stars - a.stars);

// With memo: only re-sorts when projects changes
const sorted = useMemo(
  () => [...projects].sort((a, b) => b.stars - a.stars),
  [projects]
);

Keep memo dependencies tight. A missing dependency is a stale closure bug; a spurious dependency defeats the optimization.

3. useCallback for Stable Handler References

Only matters when passing callbacks to memoized children:

const handleFilter = useCallback((tag: string) => {
  setFilter(tag);
}, []); // stable β€” setFilter identity is guaranteed by React

4. Lazy + Suspense for Code Splitting

Heavy components (charts, rich text editors, 3D scenes) should be lazily imported:

const GitHubCalendar = lazy(() => import('./GitHubCalendar'));

function StatsSection() {
  return (
    <Suspense fallback={<Skeleton />}>
      <GitHubCalendar username="supremovb" />
    </Suspense>
  );
}

This keeps the initial bundle small and defers heavy parsing until the component scrolls into view.

5. Virtualization for Long Lists

If you’re rendering > 100 items, virtualize. I use @tanstack/react-virtual β€” it’s framework-agnostic and tiny:

const rowVirtualizer = useVirtualizer({
  count: items.length,
  getScrollElement: () => parentRef.current,
  estimateSize: () => 80,
});

Only the visible rows hit the DOM. Scrolling 10k items at 60 fps becomes trivial.

What I Don’t Do

  • Wrapping every component in memo β€œjust in case”
  • useMemo on primitive values
  • Splitting components into micro-files to β€œforce” memoization boundaries
  • Micro-optimizing before the feature works

Profile, measure, optimize the bottleneck, measure again. The end.

Found this useful? Share it.