React Performance Optimization Cheat Sheet

Rendering Optimization

React.memo for Component Memoization

// Memoize component to prevent unnecessary re-renders
const ExpensiveComponent = React.memo(({ data, onAction }) => {
  return (
    <div>
      {data.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
      <button onClick={onAction}>Action</button>
    </div>
  );
});

// Custom comparison function
const CustomMemoComponent = React.memo(
  ({ data }) => <div>{data.value}</div>,
  (prevProps, nextProps) => prevProps.data.id === nextProps.data.id
);

useMemo for Expensive Calculations

function DataProcessor({ items, filter }) {
  // Memoize expensive calculation
  const processedData = useMemo(() => {
    return items
      .filter(item => item.category === filter)
      .map(item => ({
        ...item,
        processed: expensiveCalculation(item)
      }));
  }, [items, filter]);

  return <div>{processedData.map(item => <Item key={item.id} {...item} />)}</div>;
}

// Memoize object/array to prevent child re-renders
function ParentComponent({ userId }) {
  const userConfig = useMemo(() => ({
    id: userId,
    theme: 'dark',
    preferences: { notifications: true }
  }), [userId]);

  return <ChildComponent config={userConfig} />;
}

useCallback for Function Memoization

function ListComponent({ items }) {
  const [selectedId, setSelectedId] = useState(null);

  // Memoize event handlers
  const handleItemClick = useCallback((id) => {
    setSelectedId(id);
  }, []);

  const handleItemDelete = useCallback((id) => {
    // Delete logic
  }, []);

  return (
    <ul>
      {items.map(item => (
        <ListItem
          key={item.id}
          item={item}
          onClick={handleItemClick}
          onDelete={handleItemDelete}
          isSelected={selectedId === item.id}
        />
      ))}
    </ul>
  );
}

State Management Optimization

Local State Optimization

function OptimizedCounter() {
  // Use functional updates to avoid dependencies
  const [count, setCount] = useState(0);
  
  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  const reset = useCallback(() => {
    setCount(0);
  }, []);

  return (
    <div>
      <span>{count}</span>
      <button onClick={increment}>+</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Context Optimization

// Split context to prevent unnecessary re-renders
const UserContext = createContext();
const ThemeContext = createContext();

function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');

  return (
    <UserContext.Provider value={{ user, setUser }}>
      <ThemeContext.Provider value={{ theme, setTheme }}>
        <Header />
        <MainContent />
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

// Use specific context to avoid re-renders
function Header() {
  const { user } = useContext(UserContext);
  const { theme } = useContext(ThemeContext);
  
  return <header className={theme}>{user?.name}</header>;
}

Reducer Pattern for Complex State

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, action.payload]
      };
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    default:
      return state;
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, {
    todos: [],
    filter: 'all'
  });

  const addTodo = useCallback((text) => {
    dispatch({ type: 'ADD_TODO', payload: { id: Date.now(), text } });
  }, []);

  const toggleTodo = useCallback((id) => {
    dispatch({ type: 'TOGGLE_TODO', payload: id });
  }, []);

  return (
    <div>
      <TodoForm onAdd={addTodo} />
      <TodoList todos={state.todos} onToggle={toggleTodo} />
    </div>
  );
}

List and Data Optimization

Virtualization for Large Lists

import { FixedSizeList as List } from 'react-window';

function VirtualizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      <ListItem item={items[index]} />
    </div>
  );

  return (
    <List
      height={400}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </List>
  );
}

// Custom virtualization hook
function useVirtualization(items, itemHeight, containerHeight) {
  const [scrollTop, setScrollTop] = useState(0);
  
  const visibleItems = useMemo(() => {
    const startIndex = Math.floor(scrollTop / itemHeight);
    const endIndex = Math.min(
      startIndex + Math.ceil(containerHeight / itemHeight),
      items.length
    );
    
    return items.slice(startIndex, endIndex).map((item, index) => ({
      ...item,
      index: startIndex + index,
      style: {
        position: 'absolute',
        top: (startIndex + index) * itemHeight,
        height: itemHeight
      }
    }));
  }, [items, scrollTop, itemHeight, containerHeight]);

  return { visibleItems, setScrollTop };
}

Pagination and Infinite Scroll

function useInfiniteScroll(fetchData, deps = []) {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
  const [page, setPage] = useState(1);

  const loadMore = useCallback(async () => {
    if (loading || !hasMore) return;
    
    setLoading(true);
    try {
      const newData = await fetchData(page);
      setData(prev => [...prev, ...newData]);
      setHasMore(newData.length > 0);
      setPage(prev => prev + 1);
    } finally {
      setLoading(false);
    }
  }, [fetchData, page, loading, hasMore]);

  useEffect(() => {
    loadMore();
  }, deps);

  return { data, loading, hasMore, loadMore };
}

Bundle and Code Splitting

Dynamic Imports

// Lazy load components
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const ChartComponent = lazy(() => import('./ChartComponent'));

function App() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChart(true)}>Show Chart</button>
      {showChart && (
        <Suspense fallback={<div>Loading chart...</div>}>
          <ChartComponent />
        </Suspense>
      )}
    </div>
  );
}

// Route-based code splitting
const routes = {
  '/dashboard': lazy(() => import('./pages/Dashboard')),
  '/analytics': lazy(() => import('./pages/Analytics')),
  '/settings': lazy(() => import('./pages/Settings'))
};

Tree Shaking Optimization

// Import specific functions to enable tree shaking
import { useState, useEffect } from 'react';
import { debounce } from 'lodash-es';

// Instead of
// import _ from 'lodash';

// Use specific imports
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';

Image and Asset Optimization

Lazy Loading Images

function LazyImage({ src, alt, placeholder }) {
  const [isLoaded, setIsLoaded] = useState(false);
  const [imageSrc, setImageSrc] = useState(placeholder);

  useEffect(() => {
    const img = new Image();
    img.src = src;
    img.onload = () => {
      setImageSrc(src);
      setIsLoaded(true);
    };
  }, [src]);

  return (
    <img
      src={imageSrc}
      alt={alt}
      className={`lazy-image ${isLoaded ? 'loaded' : ''}`}
    />
  );
}

// Intersection Observer for lazy loading
function useIntersectionObserver(ref, options = {}) {
  const [isIntersecting, setIsIntersecting] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      setIsIntersecting(entry.isIntersecting);
    }, options);

    if (ref.current) {
      observer.observe(ref.current);
    }

    return () => observer.disconnect();
  }, [ref, options]);

  return isIntersecting;
}

Memory Management

Cleanup and Memory Leaks

function useInterval(callback, delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    if (delay !== null) {
      const id = setInterval(() => savedCallback.current(), delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

function ComponentWithCleanup() {
  const [data, setData] = useState([]);
  const abortController = useRef();

  useEffect(() => {
    abortController.current = new AbortController();
    
    fetch('/api/data', { signal: abortController.current.signal })
      .then(res => res.json())
      .then(setData)
      .catch(err => {
        if (err.name !== 'AbortError') {
          console.error(err);
        }
      });

    return () => {
      abortController.current?.abort();
    };
  }, []);

  return <div>{data.map(item => <div key={item.id}>{item.name}</div>)}</div>;
}

Performance Monitoring

React DevTools Profiler

import { Profiler } from 'react';

function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
  console.log({
    id,
    phase,
    actualDuration,
    baseDuration,
    startTime,
    commitTime
  });
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MainContent />
    </Profiler>
  );
}

Custom Performance Hooks

function usePerformanceMonitor(componentName) {
  const renderCount = useRef(0);
  
  useEffect(() => {
    renderCount.current += 1;
    console.log(`${componentName} rendered ${renderCount.current} times`);
  });

  return renderCount.current;
}

function useRenderTime() {
  const startTime = useRef(performance.now());
  
  useEffect(() => {
    const endTime = performance.now();
    console.log(`Render took ${endTime - startTime.current}ms`);
    startTime.current = performance.now();
  });
}

Best Practices

Avoid Common Pitfalls

// Good - Memoize expensive calculations
const expensiveValue = useMemo(() => {
  return heavyComputation(data);
}, [data]);

// Bad - Recalculate on every render
const expensiveValue = heavyComputation(data);

// Good - Use callback refs
const inputRef = useCallback(node => {
  if (node) {
    node.focus();
  }
}, []);

// Bad - Direct ref assignment
const inputRef = useRef();
useEffect(() => {
  if (inputRef.current) {
    inputRef.current.focus();
  }
}, []);

Bundle Size Optimization

// Use dynamic imports for large libraries
const moment = await import('moment');

// Import only what you need
import { format } from 'date-fns';

// Use webpack bundle analyzer
// npm install --save-dev webpack-bundle-analyzer

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

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
};

Production Optimizations

// Enable production mode
// NODE_ENV=production

// Use React.StrictMode in development
function App() {
  return (
    <React.StrictMode>
      <MainApp />
    </React.StrictMode>
  );
}

// Optimize images and assets
// Use WebP format, implement responsive images
// Compress and minify all assets