Advanced React Performance Patterns and Architecture: A Deep Dive
React
Performance
Architecture
TypeScript
Optimization

Advanced React Performance Patterns and Architecture: A Deep Dive

14 min read

Advanced React Performance Patterns and Architecture

Table of Contents

  1. Introduction
  2. Rendering Optimization Patterns
  3. State Management Architecture
  4. Component Design Patterns
  5. Memory Management
  6. Performance Monitoring
  7. Code Splitting Strategies
  8. Real-World Example: Virtual List Implementation
  9. Advanced Hooks Patterns
  10. Performance Testing Framework

Introduction

Building high-performance React applications requires deep understanding of React's internals, rendering behavior, and optimization techniques. This guide explores advanced patterns and architectures for building scalable React applications.

Rendering Optimization Patterns

Advanced Memoization Strategies

Memoization is a critical optimization technique in React that prevents unnecessary re-renders by caching expensive computations. This section demonstrates advanced implementation using memo, useMemo, and useCallback with custom comparison functions.

Key concepts covered:

  • Deep comparison of complex objects
  • Efficient caching of expensive computations
  • Event handler optimization
  • Render tracking for performance monitoring

The DeepComponent implementation showcases:

  • Custom comparison function for precise re-render control
  • Memoized data transformation
  • Optimized event handlers with proper dependency management
  • Performance tracking through custom hooks
// components/optimization/deep-memo.tsx
import { memo, useMemo, useCallback, useState } from "react";
import { isEqual } from "lodash";

interface DeepProps {
  data: ComplexData;
  onUpdate: (id: string, value: any) => void;
}

// Custom comparison function for complex objects
function deepPropsComparison(prevProps: DeepProps, nextProps: DeepProps) {
  return (
    isEqual(prevProps.data, nextProps.data) &&
    prevProps.onUpdate === nextProps.onUpdate
  );
}

export const DeepComponent = memo(function DeepComponent({
  data,
  onUpdate,
}: DeepProps) {
  // Expensive computation memoization
  const processedData = useMemo(() => {
    return Object.entries(data).reduce((acc, [key, value]) => {
      acc[key] = expensiveTransformation(value);
      return acc;
    }, {} as Record<string, any>);
  }, [data]);

  // Event handler memoization with multiple dependencies
  const handleUpdate = useCallback(
    (id: string, newValue: any) => {
      const transformedValue = preProcessValue(newValue);
      onUpdate(id, transformedValue);
    },
    [onUpdate]
  );

  return (
    <div>
      {Object.entries(processedData).map(([id, value]) => (
        <DataItem key={id} id={id} value={value} onUpdate={handleUpdate} />
      ))}
    </div>
  );
},
deepPropsComparison);

// Custom hook for tracking render causes
function useTrackRenders(componentName: string) {
  const renderCount = useRef(0);

  useEffect(() => {
    renderCount.current += 1;
    console.log(`${componentName} rendered:`, {
      count: renderCount.current,
      timestamp: new Date().toISOString(),
    });
  });

  return renderCount.current;
}

Virtual DOM Optimization

Virtual list implementation is crucial for handling large datasets efficiently. This pattern demonstrates:

  • Windowing technique for rendering only visible elements
  • Scroll position management
  • Dynamic height calculations
  • Efficient memory usage for large lists

Key benefits:

  • Constant memory usage regardless of list size
  • Smooth scrolling performance
  • Reduced DOM nodes
  • Optimized re-render behavior
// components/optimization/virtual-list.tsx
import { useVirtualizer } from "@tanstack/react-virtual";
import { useRef, useCallback, useState, useEffect } from "react";

interface VirtualListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  estimateSize?: number;
  overscan?: number;
}

export function VirtualList<T>({
  items,
  renderItem,
  estimateSize = 50,
  overscan = 5,
}: VirtualListProps<T>) {
  const parentRef = useRef<HTMLDivElement>(null);
  const [parentHeight, setParentHeight] = useState(0);

  // Update parent height on resize
  useEffect(() => {
    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        setParentHeight(entry.contentRect.height);
      }
    });

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

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

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: useCallback(() => estimateSize, [estimateSize]),
    overscan,
  });

  return (
    <div
      ref={parentRef}
      className="h-full overflow-auto"
      style={{
        contain: "strict",
      }}
    >
      <div
        style={{
          height: `${virtualizer.getTotalSize()}px`,
          width: "100%",
          position: "relative",
        }}
      >
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              position: "absolute",
              top: 0,
              left: 0,
              width: "100%",
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            {renderItem(items[virtualItem.index], virtualItem.index)}
          </div>
        ))}
      </div>
    </div>
  );
}

State Management Architecture

A well-designed state management architecture is fundamental for React application performance. This implementation shows:

  • Custom state manager with middleware support
  • Optimized state updates
  • Type-safe state management
  • Integration with React's rendering system

Benefits of this approach:

  • Predictable state updates
  • Reduced re-renders
  • Better debugging capabilities
  • Scalable state management solution

Advanced Context Pattern

// lib/state/create-context.ts
import {
  createContext as createReactContext,
  useContext as useReactContext,
  useCallback,
  useMemo,
  useRef,
  useEffect,
} from "react";

interface CreateContextOptions {
  strict?: boolean;
  errorMessage?: string;
  name: string;
}

export function createContext<ContextValue>(
  options: CreateContextOptions = {
    strict: true,
    name: "Context",
  }
) {
  const Context = createReactContext<ContextValue | undefined>(undefined);

  function useContext() {
    const context = useReactContext(Context);

    if (!context && options.strict) {
      const error = new Error(
        options.errorMessage ?? `${options.name} Context Provider is missing`
      );
      error.name = "ContextError";
      throw error;
    }

    return context;
  }

  return [Context.Provider, useContext] as const;
}

// Example: Advanced App State Management
interface AppState {
  theme: "light" | "dark";
  language: string;
  user: User | null;
}

interface AppStateContextValue {
  state: AppState;
  dispatch: React.Dispatch<AppAction>;
  actions: typeof appActions;
}

const [AppStateProvider, useAppState] = createContext<AppStateContextValue>({
  name: "AppState",
  strict: true,
});

// Middleware implementation for state updates
function createMiddleware<S, A extends { type: string }>(
  reducer: Reducer<S, A>,
  middlewares: Middleware<S, A>[]
) {
  return (state: S, action: A) => {
    let nextState = reducer(state, action);

    for (const middleware of middlewares) {
      nextState = middleware(state, nextState, action);
    }

    return nextState;
  };
}

// Usage with Provider
function AppStateProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(
    createMiddleware(appReducer, [loggingMiddleware, persistenceMiddleware]),
    initialState
  );

  const actions = useMemo(() => bindActionCreators(appActions, dispatch), []);

  const value = useMemo(() => ({ state, dispatch, actions }), [state, actions]);

  return <Provider value={value}>{children}</Provider>;
}

Component Design Patterns

These patterns focus on creating reusable, performant components:

  • Compound components for flexible composition
  • Render props for maximum reusability
  • Higher-order components for cross-cutting concerns
  • Custom hooks for shared logic

Compound Components Pattern

// components/patterns/select/index.tsx
import { createContext, useContext, useState, useCallback } from "react";
import { useControllableState } from "@/hooks/use-controllable-state";
import { useId } from "@/hooks/use-id";

interface SelectContext {
  selectedValue: string;
  onChange: (value: string) => void;
  isOpen: boolean;
  setIsOpen: (open: boolean) => void;
  labelId: string;
  listId: string;
}

const [SelectProvider, useSelectContext] = createContext<SelectContext>({
  name: "Select",
  strict: true,
});

interface SelectProps {
  value?: string;
  defaultValue?: string;
  onChange?: (value: string) => void;
  children: React.ReactNode;
}

export function Select({
  value,
  defaultValue,
  onChange,
  children,
}: SelectProps) {
  const [selectedValue, setSelectedValue] = useControllableState({
    value,
    defaultValue,
    onChange,
  });

  const [isOpen, setIsOpen] = useState(false);
  const labelId = useId();
  const listId = useId();

  const handleChange = useCallback(
    (newValue: string) => {
      setSelectedValue(newValue);
      setIsOpen(false);
    },
    [setSelectedValue]
  );

  const context = useMemo(
    () => ({
      selectedValue,
      onChange: handleChange,
      isOpen,
      setIsOpen,
      labelId,
      listId,
    }),
    [selectedValue, handleChange, isOpen, labelId, listId]
  );

  return <SelectProvider value={context}>{children}</SelectProvider>;
}

// Sub-components
Select.Trigger = function SelectTrigger({
  children,
  ...props
}: React.ButtonHTMLAttributes<HTMLButtonElement>) {
  const { selectedValue, isOpen, setIsOpen, labelId, listId } =
    useSelectContext();

  return (
    <button
      type="button"
      role="combobox"
      aria-expanded={isOpen}
      aria-haspopup="listbox"
      aria-labelledby={labelId}
      aria-controls={listId}
      onClick={() => setIsOpen(!isOpen)}
      {...props}
    >
      {children}
    </button>
  );
};

Select.Options = function SelectOptions({
  children,
  ...props
}: React.HTMLAttributes<HTMLUListElement>) {
  const { isOpen, listId } = useSelectContext();

  if (!isOpen) return null;

  return (
    <ul role="listbox" id={listId} {...props}>
      {children}
    </ul>
  );
};

Select.Option = function SelectOption({
  value,
  children,
  ...props
}: React.LiHTMLAttributes<HTMLLIElement> & { value: string }) {
  const { selectedValue, onChange } = useSelectContext();
  const isSelected = value === selectedValue;

  return (
    <li
      role="option"
      aria-selected={isSelected}
      onClick={() => onChange(value)}
      {...props}
    >
      {children}
    </li>
  );
};

Memory Management

Effective memory management is crucial for long-running React applications:

  • Resource cleanup strategies
  • Memory leak prevention
  • Efficient data structure usage
  • Browser memory optimization

Memory Leak Prevention

// hooks/use-memory-safe.ts
import { useEffect, useRef, useCallback } from "react";

interface MemorySafeOptions {
  timeout?: number;
  retryCount?: number;
  onError?: (error: Error) => void;
}

export function useMemorySafe<T>(
  asyncFn: () => Promise<T>,
  deps: React.DependencyList = [],
  options: MemorySafeOptions = {}
) {
  const isMounted = useRef(true);
  const abortController = useRef<AbortController>();
  const retryCount = useRef(0);

  const execute = useCallback(async () => {
    try {
      // Cleanup previous request
      abortController.current?.abort();
      abortController.current = new AbortController();

      const result = await Promise.race([
        asyncFn(),
        new Promise((_, reject) => {
          setTimeout(() => {
            reject(new Error("Request timeout"));
          }, options.timeout ?? 5000);
        }),
      ]);

      if (isMounted.current) {
        return result;
      }
    } catch (error) {
      if (error.name === "AbortError") {
        return;
      }

      if (retryCount.current < (options.retryCount ?? 3)) {
        retryCount.current += 1;
        return execute();
      }

      options.onError?.(error);
      throw error;
    }
  }, deps);

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

  return execute;
}

// Usage example
function DataComponent() {
  const fetchData = useMemorySafe(
    async () => {
      const response = await fetch("/api/data");
      return response.json();
    },
    [],
    {
      timeout: 3000,
      retryCount: 2,
      onError: (error) => {
        console.error("Failed to fetch data:", error);
      },
    }
  );

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return <div>...</div>;
}

Performance Monitoring

Comprehensive performance monitoring helps identify and resolve issues:

  • Component render tracking
  • Performance metrics collection
  • Real-time monitoring
  • Performance regression detection

Custom Performance Hooks

// hooks/use-performance-monitor.ts
import { useEffect, useRef } from "react";

interface PerformanceMetrics {
  renderTime: number;
  componentName: string;
  renderCount: number;
  timestamp: number;
}

const performanceMetrics: PerformanceMetrics[] = [];

export function usePerformanceMonitor(componentName: string) {
  const startTime = useRef<number>();
  const renderCount = useRef(0);

  useEffect(() => {
    const endTime = performance.now();

    if (startTime.current) {
      const renderTime = endTime - startTime.current;
      renderCount.current += 1;

      performanceMetrics.push({
        renderTime,
        componentName,
        renderCount: renderCount.current,
        timestamp: Date.now(),
      });

      // Report to analytics if render time exceeds threshold
      if (renderTime > 16) {
        // 60fps threshold
        console.warn(
          `Slow render detected in ${componentName}: ${renderTime.toFixed(2)}ms`
        );
      }
    }

    startTime.current = performance.now();

    return () => {
      startTime.current = undefined;
    };
  });

  return {
    getRenderCount: () => renderCount.current,
    getMetrics: () =>
      performanceMetrics.filter((m) => m.componentName === componentName),
  };
}

// Performance Boundary Component
interface PerformanceBoundaryProps {
  name: string;
  children: React.ReactNode;
  onPerformanceIssue?: (metrics: PerformanceMetrics) => void;
}

export function PerformanceBoundary({
  name,
  children,
  onPerformanceIssue,
}: PerformanceBoundaryProps) {
  const metrics = usePerformanceMonitor(name);

  useEffect(() => {
    const lastMetric = metrics.getMetrics().slice(-1)[0];
    if (lastMetric?.renderTime > 16) {
      onPerformanceIssue?.(lastMetric);
    }
  });

  return <>{children}</>;
}

Code Splitting Strategies

Advanced Dynamic Imports

// utils/dynamic-import.ts
import { lazy, Suspense } from "react";

interface ImportOptions {
  ssr?: boolean;
  loading?: React.ComponentType;
  errorBoundary?: React.ComponentType<{ error: Error }>;
  timeout?: number;
}

export function createDynamicComponent<T extends React.ComponentType<any>>(
  importFn: () => Promise<{ default: T }>,
  options: ImportOptions = {}
) {
  const LazyComponent = lazy(() => {
    if (options.timeout) {
      return Promise.race([
        importFn(),
        new Promise((_, reject) =>
          setTimeout(() => reject(new Error("Import timeout")), options.timeout)
        ),
      ]);
    }
    return importFn();
  });

  return function DynamicComponent(props: React.ComponentProps<T>) {
    if (options.ssr === false && typeof window === "undefined") {
      return null;
    }

    return (
      <Suspense fallback={options.loading ? <options.loading /> : null}>
        <ErrorBoundary fallback={options.errorBoundary ?? DefaultErrorBoundary}>
          <LazyComponent {...props} />
        </ErrorBoundary>
      </Suspense>
    );
  };
}

// Usage example
const DynamicChart = createDynamicComponent(
  () => import("@/components/Chart"),
  {
    ssr: false,
    loading: ChartSkeleton,
    errorBoundary: ChartError,
    timeout: 5000,
  }
);

Real-World Example: Virtual List Implementation

Let's implement a high-performance virtual list component that can handle large datasets efficiently.

// components/virtual-list/index.tsx
import {
  useRef,
  useEffect,
  useState,
  useMemo,
  useCallback,
  useLayoutEffect,
} from "react";
import { useResizeObserver } from "@/hooks/use-resize-observer";
import { useThrottledCallback } from "@/hooks/use-throttled-callback";

interface VirtualListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  itemHeight: number | ((item: T, index: number) => number);
  overscanCount?: number;
  onEndReached?: () => void;
  endReachedThreshold?: number;
}

export function VirtualList<T>({
  items,
  renderItem,
  itemHeight,
  overscanCount = 3,
  onEndReached,
  endReachedThreshold = 0.8,
}: VirtualListProps<T>) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [scrollTop, setScrollTop] = useState(0);
  const [containerHeight, setContainerHeight] = useState(0);

  // Calculate item positions and heights
  const itemPositions = useMemo(() => {
    let offset = 0;
    return items.map((item, index) => {
      const height =
        typeof itemHeight === "function" ? itemHeight(item, index) : itemHeight;
      const position = offset;
      offset += height;
      return { position, height };
    });
  }, [items, itemHeight]);

  // Calculate total height
  const totalHeight = useMemo(
    () =>
      itemPositions[itemPositions.length - 1]?.position +
        itemPositions[itemPositions.length - 1]?.height ?? 0,
    [itemPositions]
  );

  // Calculate visible range
  const visibleRange = useMemo(() => {
    const start = Math.max(
      0,
      findFirstVisibleIndex(
        itemPositions,
        scrollTop - overscanCount * itemHeight
      )
    );
    const end = Math.min(
      items.length,
      findLastVisibleIndex(
        itemPositions,
        scrollTop + containerHeight + overscanCount * itemHeight
      ) + 1
    );
    return { start, end };
  }, [scrollTop, containerHeight, itemPositions, overscanCount, items.length]);

  // Handle scroll
  const handleScroll = useThrottledCallback(
    (event: React.UIEvent<HTMLDivElement>) => {
      const newScrollTop = event.currentTarget.scrollTop;
      setScrollTop(newScrollTop);

      // Check if end is reached
      if (onEndReached) {
        const scrolledRatio = (newScrollTop + containerHeight) / totalHeight;
        if (scrolledRatio >= endReachedThreshold) {
          onEndReached();
        }
      }
    },
    16
  );

  // Update container height on resize
  useResizeObserver(containerRef, (entry) => {
    setContainerHeight(entry.contentRect.height);
  });

  // Render only visible items
  const visibleItems = useMemo(() => {
    return items
      .slice(visibleRange.start, visibleRange.end)
      .map((item, index) => {
        const absoluteIndex = visibleRange.start + index;
        const { position, height } = itemPositions[absoluteIndex];

        return (
          <div
            key={absoluteIndex}
            style={{
              position: "absolute",
              top: position,
              height,
              width: "100%",
            }}
          >
            {renderItem(item, absoluteIndex)}
          </div>
        );
      });
  }, [items, visibleRange, itemPositions, renderItem]);

  return (
    <div
      ref={containerRef}
      onScroll={handleScroll}
      style={{
        height: "100%",
        overflow: "auto",
        position: "relative",
      }}
    >
      <div style={{ height: totalHeight }}>{visibleItems}</div>
    </div>
  );
}

// Helper functions
function findFirstVisibleIndex(
  positions: Array<{ position: number; height: number }>,
  scrollTop: number
): number {
  let low = 0;
  let high = positions.length - 1;

  while (low <= high) {
    const mid = Math.floor((low + high) / 2);
    const { position, height } = positions[mid];

    if (position + height >= scrollTop) {
      high = mid - 1;
    } else {
      low = mid + 1;
    }
  }

  return low;
}

function findLastVisibleIndex(
  positions: Array<{ position: number; height: number }>,
  scrollBottom: number
): number {
  let low = 0;
  let high = positions.length - 1;

  while (low <= high) {
    const mid = Math.floor((low + high) / 2);
    const { position } = positions[mid];

    if (position <= scrollBottom) {
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }

  return high;
}

Advanced Hooks Patterns

Custom Hook Composition

// hooks/composition/use-async-state.ts
import { useState, useCallback } from "react";
import { useMemorySafe } from "../use-memory-safe";
import { usePerformanceMonitor } from "../use-performance-monitor";

interface AsyncState<T> {
  data: T | null;
  error: Error | null;
  isLoading: boolean;
}

interface UseAsyncStateOptions<T> {
  initialData?: T;
  onSuccess?: (data: T) => void;
  onError?: (error: Error) => void;
  transform?: (data: T) => T;
}

export function useAsyncState<T>(
  asyncFn: () => Promise<T>,
  deps: React.DependencyList = [],
  options: UseAsyncStateOptions<T> = {}
) {
  const [state, setState] = useState<AsyncState<T>>({
    data: options.initialData ?? null,
    error: null,
    isLoading: false,
  });

  const memorySafeRequest = useMemorySafe(asyncFn, deps);
  const performance = usePerformanceMonitor("useAsyncState");

  const execute = useCallback(async () => {
    setState((prev) => ({ ...prev, isLoading: true }));

    try {
      const startTime = performance.now();
      const result = await memorySafeRequest();
      const endTime = performance.now();

      const transformedData = options.transform
        ? options.transform(result)
        : result;

      setState({
        data: transformedData,
        error: null,
        isLoading: false,
      });

      options.onSuccess?.(transformedData);

      // Log performance metrics
      console.debug("Async operation completed", {
        duration: endTime - startTime,
        dataSize: JSON.stringify(result).length,
      });
    } catch (error) {
      setState({
        data: null,
        error,
        isLoading: false,
      });

      options.onError?.(error);
    }
  }, [memorySafeRequest, options]);

  return {
    ...state,
    execute,
    reset: useCallback(() => {
      setState({
        data: options.initialData ?? null,
        error: null,
        isLoading: false,
      });
    }, [options.initialData]),
  };
}

Performance Testing Framework

// tests/performance/measure.ts
import { performance, PerformanceObserver } from "perf_hooks";

interface PerformanceTestOptions {
  iterations?: number;
  warmupIterations?: number;
  timeout?: number;
}

export async function measurePerformance(
  testFn: () => Promise<void> | void,
  options: PerformanceTestOptions = {}
) {
  const { iterations = 100, warmupIterations = 10, timeout = 5000 } = options;

  const measurements: number[] = [];

  // Warmup phase
  for (let i = 0; i < warmupIterations; i++) {
    await testFn();
  }

  // Measurement phase
  for (let i = 0; i < iterations; i++) {
    const start = performance.now();

    await Promise.race([
      testFn(),
      new Promise((_, reject) =>
        setTimeout(() => reject(new Error("Test timeout")), timeout)
      ),
    ]);

    const end = performance.now();
    measurements.push(end - start);
  }

  // Calculate statistics
  const average = measurements.reduce((a, b) => a + b) / measurements.length;
  const sorted = [...measurements].sort((a, b) => a - b);
  const median = sorted[Math.floor(sorted.length / 2)];
  const p95 = sorted[Math.floor(sorted.length * 0.95)];
  const p99 = sorted[Math.floor(sorted.length * 0.99)];

  return {
    average,
    median,
    p95,
    p99,
    min: Math.min(...measurements),
    max: Math.max(...measurements),
    measurements,
  };
}

// Usage example
describe("VirtualList Performance", () => {
  it("should render 1000 items efficiently", async () => {
    const items = Array.from({ length: 1000 }, (_, i) => ({
      id: i,
      content: `Item ${i}`,
    }));

    const { p95 } = await measurePerformance(
      async () => {
        const { container } = render(
          <VirtualList
            items={items}
            renderItem={(item) => <div>{item.content}</div>}
            itemHeight={50}
          />
        );

        // Simulate scroll
        fireEvent.scroll(container.firstChild as Element, {
          target: { scrollTop: 1000 },
        });

        await waitFor(() => {
          expect(container.querySelectorAll("div").length).toBeGreaterThan(0);
        });
      },
      { iterations: 50 }
    );

    expect(p95).toBeLessThan(16); // Should render within one frame
  });
});

Conclusion

Optimizing React applications requires a deep understanding of React's internals and performance patterns. This guide provides advanced techniques for building high-performance React applications that scale.

Additional Resources