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.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply