React is fast by default, but large apps benefit from targeted optimizations.

React.memo

Prevent re-renders when props haven’t changed:

  const UserCard = React.memo(function UserCard({ user }) {
    console.log('Rendering', user.name);
    return <div>{user.name}</div>;
});

// Custom comparison
const UserCard = React.memo(UserCardComponent, (prev, next) => {
    return prev.user.id === next.user.id;
});
  

useMemo and useCallback

  function ProductList({ products, category }) {
    // Expensive filter — only recompute when deps change
    const filtered = useMemo(
        () => products.filter(p => p.category === category),
        [products, category]
    );

    const handleSelect = useCallback((id) => {
        console.log('Selected:', id);
    }, []);

    return filtered.map(p => (
        <ProductItem key={p.id} product={p} onSelect={handleSelect} />
    ));
}
  

Don’t optimize prematurely — measure first.

Code Splitting

Split bundles with lazy and Suspense:

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

function App() {
    return (
        <Suspense fallback={<Spinner />}>
            <Dashboard />
        </Suspense>
    );
}
  

Virtualization for Long Lists

Render only visible items:

  npm install @tanstack/react-virtual
  
  import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }) {
    const parentRef = useRef(null);
    const virtualizer = useVirtualizer({
        count: items.length,
        getScrollElement: () => parentRef.current,
        estimateSize: () => 50
    });

    return (
        <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
            <div style={{ height: virtualizer.getTotalSize() }}>
                {virtualizer.getVirtualItems().map(vRow => (
                    <div key={vRow.index} style={{
                        position: 'absolute',
                        top: vRow.start,
                        height: vRow.size
                    }}>
                        {items[vRow.index].name}
                    </div>
                ))}
            </div>
        </div>
    );
}
  

Profiling with React DevTools

  1. Install React Developer Tools browser extension
  2. Open Profiler tab
  3. Record interactions and identify slow components

Key Rules

  1. Keep state close to where it’s used
  2. Avoid inline objects/functions in props to memoized children
  3. Use keys correctly in lists
  4. Split large components into smaller ones
  5. Defer non-urgent updates with startTransition (React 18):
  import { startTransition } from 'react';

startTransition(() => {
    setSearchResults(filtered); // Lower priority update
});
  

Bundle Analysis

  npm run build
npx vite-bundle-visualizer
  

Identify large dependencies and lazy-load them.

Profile before optimizing — most React apps are fast enough without manual memoization.