Skip to content

Component Optimization Patterns in Modern React ​

As React applications grow in complexity, implementing the right component patterns becomes crucial for maintaining optimal performance. This guide explores advanced patterns for component optimization.

Core Optimization Patterns ​

1. Smart Component Composition ​

jsx
// Instead of prop drilling
function GrandParent({ data }) {
  return <Parent data={data} />;
}

function Parent({ data }) {
  return <Child data={data} />;
}

// Use component composition
function GrandParent({ data }) {
  const processedData = useMemo(() => processData(data), [data]);

  return (
    <DataContext.Provider value={processedData}>
      <Parent />
    </DataContext.Provider>
  );
}

function Parent() {
  return <Child />;
}

function Child() {
  const data = useContext(DataContext);
  return <div>{data.value}</div>;
}

2. Component Splitting Pattern ​

jsx
// Before: Monolithic component
function ComplexDashboard({ data, filters, sorting }) {
  return (
    <div>
      <FilterPanel filters={filters} />
      <DataGrid data={data} sorting={sorting} />
      <Analytics data={data} />
    </div>
  );
}

// After: Split components with focused updates
function Dashboard({ data, filters, sorting }) {
  const memoizedFilters = useMemo(() => <FilterPanel filters={filters} />, [filters]);
  const memoizedGrid = useMemo(() => <DataGrid data={data} sorting={sorting} />, [data, sorting]);
  const memoizedAnalytics = useMemo(() => <Analytics data={data} />, [data]);

  return (
    <div>
      {memoizedFilters}
      {memoizedGrid}
      {memoizedAnalytics}
    </div>
  );
}

Advanced Hook Patterns ​

1. Derived State Pattern ​

jsx
function useDerivedState(value, transform) {
  const [derivedValue, setDerivedValue] = useState(() => transform(value));

  useEffect(() => {
    setDerivedValue(transform(value));
  }, [value, transform]);

  return derivedValue;
}

function OptimizedList({ items }) {
  const processedItems = useDerivedState(
    items,
    useCallback((items) => {
      return items.map(processItem).filter(filterItem);
    }, [])
  );

  return (
    <ul>
      {processedItems.map((item) => (
        <ListItem key={item.id} data={item} />
      ))}
    </ul>
  );
}

2. Event Handler Pattern ​

jsx
function useEventCallback(handler) {
  const handlerRef = useRef(handler);

  useLayoutEffect(() => {
    handlerRef.current = handler;
  });

  return useCallback((...args) => {
    const fn = handlerRef.current;
    return fn(...args);
  }, []);
}

function OptimizedForm() {
  const [values, setValues] = useState({});

  const handleSubmit = useEventCallback(() => {
    submitForm(values);
  });

  return <form onSubmit={handleSubmit}>{/* Form fields */}</form>;
}

State Management Patterns ​

1. State Splitting Pattern ​

jsx
function useOptimizedState(initialState) {
  const [frequent, setFrequent] = useState(initialState.frequent);
  const [infrequent, setInfrequent] = useState(initialState.infrequent);

  const optimizedUpdate = useCallback((type, value) => {
    if (type === 'frequent') {
      setFrequent(value);
    } else {
      setInfrequent(value);
    }
  }, []);

  return { frequent, infrequent, optimizedUpdate };
}

function OptimizedComponent() {
  const { frequent, infrequent, optimizedUpdate } = useOptimizedState({
    frequent: { count: 0 },
    infrequent: { settings: {} },
  });

  return (
    <div>
      <FrequentUpdates data={frequent} onUpdate={optimizedUpdate} />
      <InfrequentUpdates data={infrequent} onUpdate={optimizedUpdate} />
    </div>
  );
}

2. Batch Update Pattern ​

jsx
function useBatchUpdates(initialState) {
  const [state, setState] = useState(initialState);
  const batchQueue = useRef([]);

  const flushBatch = useCallback(() => {
    if (batchQueue.current.length === 0) return;

    setState((prevState) => {
      return batchQueue.current.reduce(
        (acc, update) => ({
          ...acc,
          ...update,
        }),
        prevState
      );
    });

    batchQueue.current = [];
  }, []);

  const queueUpdate = useCallback(
    (update) => {
      batchQueue.current.push(update);
      requestAnimationFrame(flushBatch);
    },
    [flushBatch]
  );

  return [state, queueUpdate];
}

Render Optimization Patterns ​

1. Conditional Rendering Pattern ​

jsx
function useDeferred(value, timeout = 0) {
  const [deferredValue, setDeferredValue] = useState(value);

  useEffect(() => {
    const timeoutId = setTimeout(() => {
      setDeferredValue(value);
    }, timeout);

    return () => clearTimeout(timeoutId);
  }, [value, timeout]);

  return deferredValue;
}

function OptimizedList({ items }) {
  const deferredItems = useDeferred(items, 100);

  return (
    <div>
      {items.length > 1000 ? (
        <VirtualizedList items={deferredItems} />
      ) : (
        <SimpleList items={deferredItems} />
      )}
    </div>
  );
}

2. Progressive Loading Pattern ​

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

function ProgressiveLoader({ children, fallback }) {
  const [shouldLoad, setShouldLoad] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting) {
          setShouldLoad(true);
        }
      },
      { threshold: 0.1 }
    );

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

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

  return shouldLoad ? children : fallback;
}

Implementation Best Practices ​

  1. Component Design

    • Keep components focused
    • Implement proper prop types
    • Use composition effectively
    • Split complex logic
  2. State Management

    • Minimize state updates
    • Use appropriate patterns
    • Implement proper batching
    • Optimize context usage
  3. Render Control

    • Implement smart memoization
    • Control re-render triggers
    • Use proper keys
    • Optimize lists

Performance Monitoring ​

jsx
const withPerformanceTracking = (WrappedComponent, options = {}) => {
  return function PerformanceTrackedComponent(props) {
    useEffect(() => {
      const start = performance.now();

      return () => {
        const duration = performance.now() - start;
        if (duration > options.threshold) {
          console.warn(`${WrappedComponent.name} took ${duration}ms to render`);
        }
      };
    });

    return <WrappedComponent {...props} />;
  };
};

Conclusion ​

Implementing the right component optimization patterns is crucial for building performant React applications. These patterns provide a foundation for creating efficient, maintainable components that scale well with your application's growth.

Key takeaways:

  • Choose patterns based on specific use cases
  • Implement optimizations progressively
  • Monitor performance impact
  • Balance complexity with benefits

Additional Resources ​

Released under the MIT License.