React Performance Optimization: Every Technique From memo to Virtualization

React Performance Optimization: Every Technique From memo to Virtualization

React performance problems are predictable, and so are their solutions. 90% of React slowdowns come from four causes: unnecessary component re-renders, missing memoization on expensive computations, rendering 10,000 items in a list instead of virtualizing, and shipping 5MB JavaScript bundles. This guide covers every optimization technique with clear guidance on when to apply each.

TL;DR: Profile with React DevTools Profiler before optimizing. Use React.memo to prevent child re-renders. Use useMemo for expensive computations. Use useCallback for stable function references. Virtualize lists over 100 items. Code-split with React.lazy. Never define objects/arrays inline in JSX.

Profile first with React DevTools

// Enable React DevTools Profiler:
// 1. Open DevTools → Profiler tab
// 2. Click Record
// 3. Interact with slow UI
// 4. Click Stop
// 5. Look for: components with long render times, components
//    that render when they shouldn't, renders triggered by parent

// The Flamegraph shows:
// - Gray bars: did not render this commit
// - Yellow/orange bars: slow renders
// - How many ms each component took
// - Why it re-rendered (props changed? state changed? parent re-rendered?)

React.memo — prevent unnecessary re-renders

// Without memo: re-renders every time parent renders
function ExpensiveChild({ data }) {
  // Heavy computation or large render tree
  return 
{data.map(item => )}
; } // With memo: only re-renders when data prop changes const ExpensiveChild = React.memo(function({ data }) { return
{data.map(item => )}
; }); // Custom comparison for deep equality: const ExpensiveChild = React.memo(function({ user }) { return ; }, (prevProps, nextProps) => { // Return true = do NOT re-render return prevProps.user.id === nextProps.user.id && prevProps.user.updatedAt === nextProps.user.updatedAt; }); // When to use React.memo: // - Component renders often // - Component has expensive render // - Props rarely change // When NOT to use: // - Component always re-renders with new props anyway // - Comparison cost > render cost (simple components)

useMemo and useCallback correctly

// useMemo: memoize expensive computed values
function ProductList({ products, searchQuery, sortBy }) {
  // Without useMemo: recalculates on every render
  // With useMemo: only recalculates when products, searchQuery, or sortBy changes
  const filteredAndSorted = useMemo(() => {
    return products
      .filter(p => p.name.toLowerCase().includes(searchQuery.toLowerCase()))
      .sort((a, b) => a[sortBy] > b[sortBy] ? 1 : -1);
  }, [products, searchQuery, sortBy]);

  return 
    {filteredAndSorted.map(p => )}
; } // useCallback: stable function reference for child components function Parent() { const [count, setCount] = useState(0); // Without useCallback: new function instance every render // Child with memo would still re-render because handler is "new" const handleClick = useCallback((id) => { deleteItem(id); }, []); // Empty deps: function never changes return ; } // MISTAKE: useMemo for everything — adds overhead // Only use useMemo when: // - Computation takes >1ms (filter/sort large arrays, complex transforms) // - Value passed as prop to memoized child component

The inline object/array problem

// EVERY render creates a NEW object reference
// React.memo children will ALWAYS re-render

// Bad: new object on every render
 setHovered(true)}        // New function reference every render
/>

// Good: stable references
const CHART_STYLE = { color: "red", margin: 20 }; // Module level (never changes)
const DEFAULT_DATA = [1, 2, 3]; // Module level

function Parent() {
  const handleHover = useCallback(() => setHovered(true), []);
  return ;
}

List virtualization — only render visible items

import { FixedSizeList } from 'react-window';

// BAD: renders 10,000 DOM nodes
function BadList({ items }) {
  return (
    
    {items.map(item => )}
); // 10,000 DOM nodes: 3-5 second initial render, 500MB memory } // GOOD: renders only ~20 visible items (the viewport) function VirtualizedList({ items }) { const Row = ({ index, style }) => (
); return ( {Row} ); // Only ~8 DOM nodes visible at once, regardless of list size // Rule: virtualize any list with >100 items }

Code splitting with React.lazy

import { lazy, Suspense } from 'react';

// Without code splitting: entire dashboard loaded on first visit
import HeavyDashboard from './Dashboard';

// With code splitting: Dashboard only loaded when navigated to
const HeavyDashboard = lazy(() => import('./Dashboard'));
const ReportsPage = lazy(() => import('./Reports'));
const AdminPanel = lazy(() => import('./Admin'));

function App() {
  return (
    }>
      
        } />
        } />
      
    
  );
}
// Initial bundle: 200KB instead of 2MB
// Dashboard chunk: loaded on demand (~1.5MB separate)

React performance checklist

  • ✅ Profile with React DevTools Profiler before adding any optimization
  • React.memo for components that render often with stable props
  • useMemo for expensive computations (filter/sort of large arrays)
  • useCallback for functions passed as props to memoized children
  • ✅ Never define objects, arrays, or functions inline in JSX props
  • ✅ Virtualize any list with more than 100 items (react-window or react-virtual)
  • ✅ Code-split every route with React.lazy + Suspense
  • ✅ Use key correctly — stable unique IDs, never array index
  • ❌ Don’t use useMemo for cheap operations — the overhead can exceed the saving
  • ❌ Don’t memoize everything — profile first, optimize second

React performance is closely tied to JavaScript fundamentals — the WeakMap and WeakRef guide explains the memory model that makes React memo’s shallow comparison fast. For build-time optimization, V8 JIT optimization explains why monomorphic component props compile faster. External reference: React rendering docs.

Recommended Books

Designing Data-Intensive Applications — The essential deep-dive on distributed systems, databases, and production engineering at scale.

The Pragmatic Programmer — Timeless principles for writing better code, debugging smarter, and advancing as an engineer.

Affiliate links. We earn a small commission at no extra cost to you.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.