React Performance Optimization

1. React.memo for Component Memoization

Concept Description When to Use Performance Impact
React.memo HOC that memoizes component result Expensive renders, pure components Skips re-render if props unchanged
Shallow comparison Default props comparison (===) Primitive props Fast, works for most cases
Custom comparison Custom arePropsEqual function Complex props, deep equality Slower, use judiciously
When NOT to use Simple components, always different props Overhead > benefit Can hurt performance
Props Type Memo Behavior Solution
Primitives Compared by value Works perfectly
Objects Compared by reference useMemo to stabilize
Arrays Compared by reference useMemo to stabilize
Functions Compared by reference useCallback to stabilize
Children prop New reference each render Extract to separate component

Example: React.memo usage patterns

// Basic React.memo usage
const ExpensiveComponent = React.memo(({ data }) => {
  console.log('Rendering ExpensiveComponent');
  
  // Expensive computation or large render tree
  const processedData = data.map(item => {
    // Complex processing
    return item.value * 2;
  });
  
  return (
    <div>
      {processedData.map(val => (
        <div key={val}>{val}</div>
      ))}
    </div>
  );
});

// Parent component
const Parent = () => {
  const [count, setCount] = useState(0);
  const [data] = useState([1, 2, 3, 4, 5]);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      {/* ExpensiveComponent won't re-render when count changes */}
      <ExpensiveComponent data={data} />
    </div>
  );
};

// ❌ BAD: memo doesn't help with new object references
const BadMemo = React.memo(({ config }) => {
  return <div>{config.name}</div>;
});

const ParentBad = () => {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {/* Re-renders every time - new object! */}
      <BadMemo config={{ name: 'Test' }} />
    </div>
  );
};

// ✅ GOOD: stabilize object with useMemo
const GoodMemo = React.memo(({ config }) => {
  return <div>{config.name}</div>;
});

const ParentGood = () => {
  const [count, setCount] = useState(0);
  
  const config = useMemo(() => ({ name: 'Test' }), []);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {/* Only renders once! */}
      <GoodMemo config={config} />
    </div>
  );
};

// Custom comparison function
const UserCard = React.memo(
  ({ user, onEdit }) => {
    return (
      <div>
        <h3>{user.name}</h3>
        <p>{user.email}</p>
        <button onClick={() => onEdit(user.id)}>Edit</button>
      </div>
    );
  },
  (prevProps, nextProps) => {
    // Custom comparison: only re-render if user.id or user.name changed
    return (
      prevProps.user.id === nextProps.user.id &&
      prevProps.user.name === nextProps.user.name
    );
    // Return true to skip render, false to render
  }
);

// When NOT to use React.memo
const SimpleComponent = ({ text }) => {
  return <div>{text}</div>;
};
// No need for memo - component is very simple

const AlwaysChanging = ({ timestamp }) => {
  return <div>{timestamp}</div>;
};
// No need for memo - timestamp always changes

// Memoizing list items
const TodoItem = React.memo(({ todo, onToggle, onDelete }) => {
  console.log('Rendering todo:', todo.id);
  
  return (
    <li>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      {todo.text}
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </li>
  );
});

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  
  // Stabilize callback functions
  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  }, []);
  
  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  }, []);
  
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={handleToggle}
          onDelete={handleDelete}
        />
      ))}
    </ul>
  );
};
Note: React.memo only checks props changes. It doesn't prevent re-renders from state/context changes within the component. Use it for expensive components with stable props.

2. useMemo Hook for Expensive Calculations

Use Case Without useMemo With useMemo Benefit
Expensive calculation Runs every render Cached until deps change Saves computation time
Array filtering/sorting Creates new array every render Reuses array if deps unchanged Prevents unnecessary work
Object creation New reference every render Stable reference Prevents child re-renders
Derived state Computed on each render Computed only when needed Performance optimization
When to Use When NOT to Use
Expensive calculations (loops, recursion) Simple calculations (arithmetic)
Processing large arrays/objects Small data sets (< 100 items)
Stabilizing references for child props Values that always change
Complex transformations/filtering Trivial operations

Example: useMemo patterns

// Expensive calculation
const ProductList = ({ products, filter }) => {
  // Without useMemo: runs on EVERY render (bad!)
  // const filtered = products.filter(p => p.category === filter);
  
  // With useMemo: only runs when products or filter changes
  const filteredProducts = useMemo(() => {
    console.log('Filtering products...');
    return products.filter(p => p.category === filter);
  }, [products, filter]);
  
  return (
    <ul>
      {filteredProducts.map(p => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
};

// Complex derived state
const Dashboard = ({ data }) => {
  const statistics = useMemo(() => {
    console.log('Computing statistics...');
    
    const total = data.reduce((sum, item) => sum + item.value, 0);
    const average = total / data.length;
    const max = Math.max(...data.map(item => item.value));
    const min = Math.min(...data.map(item => item.value));
    
    return { total, average, max, min };
  }, [data]);
  
  return (
    <div>
      <p>Total: {statistics.total}</p>
      <p>Average: {statistics.average}</p>
      <p>Max: {statistics.max}</p>
      <p>Min: {statistics.min}</p>
    </div>
  );
};

// Sorting large lists
const SortableTable = ({ data, sortBy, sortOrder }) => {
  const sortedData = useMemo(() => {
    console.log('Sorting data...');
    
    return [...data].sort((a, b) => {
      const aVal = a[sortBy];
      const bVal = b[sortBy];
      
      if (typeof aVal === 'string') {
        return sortOrder === 'asc'
          ? aVal.localeCompare(bVal)
          : bVal.localeCompare(aVal);
      }
      
      return sortOrder === 'asc' ? aVal - bVal : bVal - aVal;
    });
  }, [data, sortBy, sortOrder]);
  
  return (
    <table>
      {sortedData.map(row => (
        <tr key={row.id}>
          <td>{row.name}</td>
        </tr>
      ))}
    </table>
  );
};

// Stabilizing object references
const Parent = () => {
  const [count, setCount] = useState(0);
  
  // ❌ BAD: new object every render
  // const config = { theme: 'dark', lang: 'en' };
  
  // ✅ GOOD: stable reference
  const config = useMemo(
    () => ({ theme: 'dark', lang: 'en' }),
    []
  );
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <MemoizedChild config={config} />
    </div>
  );
};

// When NOT to use useMemo
const Bad = ({ a, b }) => {
  // ❌ Overkill - simple calculation
  const sum = useMemo(() => a + b, [a, b]);
  
  // ✅ Just do it directly
  const betterSum = a + b;
  
  return <div>{sum}</div>;
};

// Search and filter with multiple criteria
const AdvancedSearch = ({ items, search, filters }) => {
  const filteredItems = useMemo(() => {
    console.log('Filtering items...');
    
    return items.filter(item => {
      // Text search
      if (search && !item.name.toLowerCase().includes(search.toLowerCase())) {
        return false;
      }
      
      // Category filter
      if (filters.category && item.category !== filters.category) {
        return false;
      }
      
      // Price range
      if (item.price < filters.minPrice || item.price > filters.maxPrice) {
        return false;
      }
      
      return true;
    });
  }, [items, search, filters]);
  
  const sortedItems = useMemo(() => {
    console.log('Sorting items...');
    return [...filteredItems].sort((a, b) => a.name.localeCompare(b.name));
  }, [filteredItems]);
  
  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id}>{item.name} - ${item.price}</li>
      ))}
    </ul>
  );
};

// Recursive computation
const TreeView = ({ node }) => {
  const descendantCount = useMemo(() => {
    const countDescendants = (n) => {
      if (!n.children) return 0;
      return n.children.length + 
        n.children.reduce((sum, child) => sum + countDescendants(child), 0);
    };
    return countDescendants(node);
  }, [node]);
  
  return <div>Descendants: {descendantCount}</div>;
};
Warning: Don't overuse useMemo! It has overhead (memory + comparison). Profile first, optimize later. Most calculations are fast enough without memoization.

3. useCallback Hook for Function Memoization

Scenario Without useCallback With useCallback Impact
Function prop to memo component New function = child re-renders Stable function = no re-render Prevents unnecessary renders
Function in useEffect deps Effect runs every render Effect runs only when needed Reduces side effects
Event handlers New function every render Stable reference Slight memory improvement
When to Use useCallback When NOT to Use
Function passed to memoized child Simple event handlers not passed down
Function in useEffect dependencies Function only used once
Function passed to many children Non-memoized children
Optimization after profiling Premature optimization

Example: useCallback patterns

// Basic useCallback usage
const Parent = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  // ❌ BAD: new function every render
  // const handleClick = () => {
  //   console.log(count);
  // };
  
  // ✅ GOOD: stable function reference
  const handleClick = useCallback(() => {
    console.log(count);
  }, [count]); // Re-created only when count changes
  
  return (
    <div>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {/* Child won't re-render when text changes */}
      <MemoizedChild onClick={handleClick} />
    </div>
  );
};

const MemoizedChild = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click me</button>;
});

// Function in useEffect dependencies
const DataFetcher = ({ userId }) => {
  const [data, setData] = useState(null);
  
  // Stable fetchData function
  const fetchData = useCallback(async () => {
    const response = await fetch(\`/api/users/\${userId}\`);
    const result = await response.json();
    setData(result);
  }, [userId]);
  
  useEffect(() => {
    fetchData();
  }, [fetchData]); // Only runs when fetchData changes (i.e., when userId changes)
  
  return <div>{data?.name}</div>;
};

// List operations with callbacks
const TodoList = () => {
  const [todos, setTodos] = useState([]);
  
  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  }, []); // Empty deps - uses functional update
  
  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  }, []);
  
  const handleEdit = useCallback((id, newText) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, text: newText } : todo
    ));
  }, []);
  
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={handleToggle}
          onDelete={handleDelete}
          onEdit={handleEdit}
        />
      ))}
    </ul>
  );
};

// When NOT to use useCallback
const SimpleComponent = () => {
  const [count, setCount] = useState(0);
  
  // ❌ Unnecessary - not passed to memoized child
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);
  
  // ✅ Just use inline
  return (
    <button onClick={() => setCount(c => c + 1)}>
      {count}
    </button>
  );
};

// Combining useCallback with other hooks
const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  const search = useCallback(async (searchQuery) => {
    if (!searchQuery) {
      setResults([]);
      return;
    }
    
    const response = await fetch(\`/api/search?q=\${searchQuery}\`);
    const data = await response.json();
    setResults(data);
  }, []);
  
  // Debounced search
  useEffect(() => {
    const timer = setTimeout(() => {
      search(query);
    }, 300);
    
    return () => clearTimeout(timer);
  }, [query, search]);
  
  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
      />
      <ul>
        {results.map(r => (
          <li key={r.id}>{r.title}</li>
        ))}
      </ul>
    </div>
  );
};

// useCallback with parameters from closure
const ItemList = ({ items, onUpdate }) => {
  const [filter, setFilter] = useState('');
  
  // ❌ BAD: depends on filter but not in deps
  // const handleUpdate = useCallback((id, value) => {
  //   if (filter === 'active') {
  //     onUpdate(id, value);
  //   }
  // }, []); // Missing filter!
  
  // ✅ GOOD: includes all dependencies
  const handleUpdate = useCallback((id, value) => {
    if (filter === 'active') {
      onUpdate(id, value);
    }
  }, [filter, onUpdate]);
  
  return (
    <ul>
      {items.map(item => (
        <Item key={item.id} item={item} onUpdate={handleUpdate} />
      ))}
    </ul>
  );
};
Note: useCallback returns memoized function, useMemo returns memoized value. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

4. Code Splitting with React.lazy and Suspense

Technique Description Bundle Impact Use Case
React.lazy Dynamic import for components Separate chunk per lazy component Route-based splitting
Suspense Loading fallback for lazy components No impact Show loading state
Route-based Split by routes/pages One chunk per route Most common pattern
Component-based Split heavy components Smaller chunks Modals, tabs, charts
Library splitting Separate vendor chunks Better caching Large dependencies

Example: Code splitting patterns

// Basic React.lazy usage
import { lazy, Suspense } from 'react';

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

const App = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
};

// Route-based code splitting
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));

const AppRouter = () => {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/profile" element={<Profile />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
};

// Conditional lazy loading
const AdminPanel = lazy(() => import('./AdminPanel'));

const App = ({ user }) => {
  return (
    <div>
      {user.isAdmin && (
        <Suspense fallback={<div>Loading admin panel...</div>}>
          <AdminPanel />
        </Suspense>
      )}
    </div>
  );
};

// Named exports with lazy
const LazyComponent = lazy(() => 
  import('./MyComponent').then(module => ({
    default: module.MyNamedComponent
  }))
);

// Preloading lazy components
const HeavyChart = lazy(() => import('./HeavyChart'));

// Preload function
const preloadChart = () => {
  import('./HeavyChart');
};

const Dashboard = () => {
  const [showChart, setShowChart] = useState(false);
  
  return (
    <div>
      {/* Preload on hover */}
      <button
        onClick={() => setShowChart(true)}
        onMouseEnter={preloadChart}
      >
        Show Chart
      </button>
      
      {showChart && (
        <Suspense fallback={<div>Loading chart...</div>}>
          <HeavyChart />
        </Suspense>
      )}
    </div>
  );
};

// Modal lazy loading
const Modal = lazy(() => import('./Modal'));

const App = () => {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <div>
      <button onClick={() => setIsOpen(true)}>Open Modal</button>
      
      {isOpen && (
        <Suspense fallback={null}>
          <Modal onClose={() => setIsOpen(false)} />
        </Suspense>
      )}
    </div>
  );
};

// Tab-based splitting
const Tab1 = lazy(() => import('./tabs/Tab1'));
const Tab2 = lazy(() => import('./tabs/Tab2'));
const Tab3 = lazy(() => import('./tabs/Tab3'));

const TabbedInterface = () => {
  const [activeTab, setActiveTab] = useState('tab1');
  
  return (
    <div>
      <div>
        <button onClick={() => setActiveTab('tab1')}>Tab 1</button>
        <button onClick={() => setActiveTab('tab2')}>Tab 2</button>
        <button onClick={() => setActiveTab('tab3')}>Tab 3</button>
      </div>
      
      <Suspense fallback={<div>Loading tab...</div>}>
        {activeTab === 'tab1' && <Tab1 />}
        {activeTab === 'tab2' && <Tab2 />}
        {activeTab === 'tab3' && <Tab3 />}
      </Suspense>
    </div>
  );
};

// Error boundary with lazy loading
class ErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return <div>Failed to load component</div>;
    }
    return this.props.children;
  }
}

const AppWithErrorHandling = () => {
  return (
    <ErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </ErrorBoundary>
  );
};

// Webpack magic comments for chunk naming
const Dashboard = lazy(() =>
  import(/* webpackChunkName: "dashboard" */ './Dashboard')
);

const AdminPanel = lazy(() =>
  import(/* webpackChunkName: "admin" */ './AdminPanel')
);

// Prefetching/preloading
const Settings = lazy(() =>
  import(
    /* webpackChunkName: "settings" */
    /* webpackPrefetch: true */
    './Settings'
  )
);
Note: Code splitting works best for route-based splits and heavy components (charts, editors, admin panels). Don't split every component - bundle overhead can outweigh benefits for small components.

5. Bundle Size Analysis and Optimization

Tool Purpose Install/Usage
webpack-bundle-analyzer Visualize bundle contents npm i -D webpack-bundle-analyzer
source-map-explorer Analyze source maps npm i -D source-map-explorer
Bundle Buddy Find duplicate dependencies Upload webpack stats.json
Lighthouse Performance audit Chrome DevTools
Optimization Technique Impact
Tree shaking Remove unused exports Smaller bundle (10-30%)
Minification Compress code 40-60% size reduction
Compression (gzip/brotli) Server-side compression 60-80% transfer size reduction
Dynamic imports Load code on demand Faster initial load
Remove unused deps Audit package.json Variable savings
Lighter alternatives Replace heavy libraries Significant size reduction

Example: Bundle optimization techniques

// package.json - analyze bundle
{
  "scripts": {
    "analyze": "source-map-explorer 'build/static/js/*.js'",
    "build:analyze": "npm run build && npm run analyze"
  }
}

// webpack.config.js - bundle analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
      reportFilename: 'bundle-report.html'
    })
  ]
};

// Tree shaking - import only what you need
// ❌ BAD: imports entire library
import _ from 'lodash';
const result = _.debounce(fn, 300);

// ✅ GOOD: import specific function
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);

// Or use lodash-es for better tree shaking
import { debounce } from 'lodash-es';

// Replace heavy libraries with lighter alternatives
// ❌ Moment.js (large)
import moment from 'moment';

// ✅ date-fns (smaller, tree-shakeable)
import { format, parseISO } from 'date-fns';

// ✅ dayjs (smallest)
import dayjs from 'dayjs';

// Remove unused dependencies
// Run: npm ls
// Check: npx depcheck

// Dynamic imports for heavy dependencies
const loadChartLibrary = async () => {
  const Chart = await import('chart.js');
  return Chart.default;
};

// Optimize images
// Use next/image or similar
import Image from 'next/image';

<Image
  src="/photo.jpg"
  width={500}
  height={300}
  loading="lazy"
  alt="Photo"
/>

// Code splitting by route
const routes = [
  {
    path: '/',
    component: lazy(() => import('./pages/Home'))
  },
  {
    path: '/dashboard',
    component: lazy(() => import('./pages/Dashboard'))
  }
];

// Vendor chunk separation (webpack)
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        priority: 10
      },
      common: {
        minChunks: 2,
        priority: 5,
        reuseExistingChunk: true
      }
    }
  }
}

// Preload critical resources
<link rel="preload" as="script" href="/main.js" />
<link rel="prefetch" href="/secondary.js" />

// Compression middleware (Express)
const compression = require('compression');
app.use(compression());

// Service worker for caching
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

// Remove console logs in production
// babel-plugin-transform-remove-console
{
  "plugins": [
    ["transform-remove-console", { "exclude": ["error", "warn"] }]
  ]
}
Warning: Always analyze your bundle before optimizing. Focus on the biggest chunks first. Remember: premature optimization is the root of all evil!

6. Re-render Optimization and React Profiler

Tool Purpose How to Use
React DevTools Profiler Record and analyze renders Click "Profiler" tab, record session
Highlight Updates Visualize re-renders Settings → "Highlight updates"
Component Stack See render cause Click component in Profiler
Why Did You Render Debug unnecessary renders npm package with detailed logs
Common Issue Symptom Solution
Inline functions Child re-renders unnecessarily useCallback + React.memo
Inline objects Props always different useMemo for stable reference
Context changes All consumers re-render Split contexts, memoize value
State too high Entire tree re-renders Move state closer to usage
Large lists Slow rendering Virtualization, pagination
No keys/bad keys Wrong items update Stable unique keys

Example: Debugging and fixing re-renders

// Install why-did-you-render
import React from 'react';

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  });
}

// Use in component
const MyComponent = ({ value }) => {
  return <div>{value}</div>;
};

MyComponent.whyDidYouRender = true;

// Finding render causes with useEffect
const DebugComponent = ({ prop1, prop2, prop3 }) => {
  useEffect(() => {
    console.log('Component rendered');
    console.log('prop1:', prop1);
    console.log('prop2:', prop2);
    console.log('prop3:', prop3);
  });
  
  return <div>Debug</div>;
};

// Custom hook to track prop changes
const useWhyDidYouUpdate = (name, props) => {
  const previousProps = useRef();
  
  useEffect(() => {
    if (previousProps.current) {
      const allKeys = Object.keys({ ...previousProps.current, ...props });
      const changedProps = {};
      
      allKeys.forEach(key => {
        if (previousProps.current[key] !== props[key]) {
          changedProps[key] = {
            from: previousProps.current[key],
            to: props[key]
          };
        }
      });
      
      if (Object.keys(changedProps).length > 0) {
        console.log('[why-did-you-update]', name, changedProps);
      }
    }
    
    previousProps.current = props;
  });
};

// Usage
const MyComponent = (props) => {
  useWhyDidYouUpdate('MyComponent', props);
  return <div>{props.value}</div>;
};

// Fix: Inline object props
// ❌ BAD
const Parent = () => {
  return <Child style={{ color: 'red' }} />; // New object every render
};

// ✅ GOOD
const Parent = () => {
  const style = useMemo(() => ({ color: 'red' }), []);
  return <Child style={style} />;
};

// Fix: Context re-renders
// ❌ BAD: value changes every render
const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

// ✅ GOOD: stable value reference
const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  
  const value = useMemo(
    () => ({ theme, setTheme }),
    [theme]
  );
  
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
};

// Fix: State colocation
// ❌ BAD: state too high
const App = () => {
  const [formData, setFormData] = useState({});
  
  return (
    <div>
      <Header /> {/* Re-renders on formData change */}
      <Sidebar /> {/* Re-renders on formData change */}
      <Form data={formData} onChange={setFormData} />
    </div>
  );
};

// ✅ GOOD: state colocated
const App = () => {
  return (
    <div>
      <Header />
      <Sidebar />
      <FormContainer /> {/* State inside here */}
    </div>
  );
};

const FormContainer = () => {
  const [formData, setFormData] = useState({});
  return <Form data={formData} onChange={setFormData} />;
};

// Profiling with React.Profiler API
const App = () => {
  const onRenderCallback = (
    id, // component name
    phase, // "mount" or "update"
    actualDuration, // time spent rendering
    baseDuration, // estimated time without memoization
    startTime, // when started
    commitTime, // when committed
    interactions // Set of interactions
  ) => {
    console.log(\`\${id} took \${actualDuration}ms to render\`);
  };
  
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MyApp />
    </Profiler>
  );
};

Performance Optimization Checklist