React Performance Patterns: From Memo to Virtualization

React Performance Patterns: From Memo to Virtualization

Ihor (Harry) Chyshkala
Ihor (Harry) ChyshkalaAuthor
|1 min read

Performance optimization in React applications requires a deep understanding of rendering behavior and various optimization techniques. In this article, we'll explore different performance patterns, from basic memoization to advanced virtualization techniques.

Understanding React's Rendering Behavior

Before diving into optimization patterns, let's understand when and why React components re-render:

javascript
1function ParentComponent() {
2  const [count, setCount] = useState(0);
3  
4  return (
5    <div>
6      <Counter count={count} />
7      <ExpensiveComponent /> {/* Re-renders on every count change */}
8    </div>
9  );
10}
11
12function ExpensiveComponent() {
13  // Expensive calculations or rendering
14  return <div>{/* ... */}</div>;
15}

Memoization Patterns

Using React.memo

typescript(29 lines)
1// Before optimization
2function MovieCard({ title, rating, onFavorite }: MovieCardProps) {
3  return (
4    <div className="card">
5      <h3>{title}</h3>
6      <div>{rating} stars</div>
7      <button onClick={onFavorite}>Favorite</button>
8    </div>

Optimizing with useMemo

typescript(16 lines)
1function SearchResults({ items, query }: SearchResultsProps) {
2  const filteredItems = useMemo(() => {
3    console.log('Filtering items...'); // Expensive operation
4    return items.filter(item =>
5      item.title.toLowerCase().includes(query.toLowerCase())
6    );
7  }, [items, query]);
8

useCallback for Event Handlers

typescript(27 lines)
1function ProductList({ products }: ProductListProps) {
2  const [favorites, setFavorites] = useState<Set<string>>(new Set());
3
4  const handleFavorite = useCallback((productId: string) => {
5    setFavorites(prev => {
6      const next = new Set(prev);
7      if (next.has(productId)) {
8        next.delete(productId);

Debouncing and Throttling

Custom Debounce Hook

typescript(34 lines)
1function useDebounce<T>(value: T, delay: number): T {
2  const [debouncedValue, setDebouncedValue] = useState(value);
3
4  useEffect(() => {
5    const timer = setTimeout(() => {
6      setDebouncedValue(value);
7    }, delay);
8

Custom Throttle Hook

typescript(20 lines)
1function useThrottle<T>(value: T, interval: number): T {
2  const [throttledValue, setThrottledValue] = useState(value);
3  const lastExecuted = useRef<number>(Date.now());
4
5  useEffect(() => {
6    const handler = setTimeout(() => {
7      const now = Date.now();
8      if (now >= lastExecuted.current + interval) {

Virtualization Techniques

Basic Virtualization Hook

typescript(78 lines)
1interface UseVirtualizationProps {
2  itemCount: number;
3  itemHeight: number;
4  containerHeight: number;
5  overscan?: number;
6}
7
8function useVirtualization({

Code Splitting and Lazy Loading

Route-Based Code Splitting

typescript(17 lines)
1import { lazy, Suspense } from 'react';
2
3const Dashboard = lazy(() => import('./pages/Dashboard'));
4const Profile = lazy(() => import('./pages/Profile'));
5const Settings = lazy(() => import('./pages/Settings'));
6
7function App() {
8  return (

Component-Based Code Splitting

typescript(16 lines)
1const HeavyChart = lazy(() => import('./components/HeavyChart'));
2
3function Dashboard() {
4  const [showChart, setShowChart] = useState(false);
5
6  return (
7    <div>
8      <button onClick={() => setShowChart(true)}>Show Chart</button>

Performance Monitoring

Custom Performance Hook

typescript(19 lines)
1function usePerformanceMonitor(componentName: string) {
2  const renderCount = useRef(0);
3  const lastRenderTime = useRef(performance.now());
4
5  useEffect(() => {
6    const renderTime = performance.now() - lastRenderTime.current;
7    renderCount.current += 1;
8

Measuring Re-Renders

Best Practices and Guidelines

  1. When to Use Memoization
    • For expensive calculations
    • For preventing unnecessary re-renders
    • When props are stable
typescript
1// Good use case
2const expensiveValue = useMemo(() => {
3  return someExpensiveCalculation(props.data);
4}, [props.data]);
5
6// Bad use case (over-optimization)
7const simpleValue = useMemo(() => {
8  return props.value + 1;
9}, [props.value]);

2. Virtualization Considerations

  • Use for large lists (100+ items)
  • Consider variable height items
  • Handle scroll restoration

3. Code Splitting Guidelines

  • Split by route
  • Split by feature
  • Split by viewport visibility

4. Performance Testing

typescript
1describe('Performance Tests', () => {
2  it('should render list efficiently', async () => {
3    const startTime = performance.now();
4    
5    render(<VirtualizedList items={largeDataSet} />);
6    
7    const endTime = performance.now();
8    expect(endTime - startTime).toBeLessThan(100);
9  });
10});

Conclusion

React performance optimization is a balance between:

  • Code complexity
  • Bundle size
  • Runtime performance
  • Development experience

Key takeaways:

  1. Use memoization judiciously
  2. Implement virtualization for large lists
  3. Split code based on user needs
  4. Monitor and measure performance
  5. Focus on user-perceived performance

About the Author

Ihor (Harry) Chyshkala

Ihor (Harry) Chyshkala

Code Alchemist: Transmuting Ideas into Reality with JS & PHP. DevOps Wizard: Transforming Infrastructure into Cloud Gold | Orchestrating CI/CD Magic | Crafting Automation Elixirs