How to Optimize Strict Mode in React

React Strict Mode is a development-only tool that helps developers find potential bugs, memory leaks, and outdated practices by intentionally double-rendering components and double-invoking effects. While it does not affect production builds, it can sometimes cause performance lag or unexpected behavior during development if your codebase is not prepared for it. This article explains how to optimize your code to work efficiently with React Strict Mode, eliminate unnecessary re-renders, and ensure a smooth development experience.

1. Ensure Component Purity

React’s Strict Mode double-invokes constructor functions, render methods, and state updater functions to detect side effects. If your components are “impure” (meaning they modify external variables or state during the render phase), Strict Mode will expose this by causing unpredictable UI bugs.

To optimize: * Keep renders pure: Never mutate props, state, or local variables outside the component’s scope during render. * Keep state updates deterministic: State updater functions (e.g., setCount(prev => prev + 1)) must not trigger side effects like API calls or manual DOM manipulations.

2. Implement Proper Cleanup in useEffect

Strict Mode mounts, unmounts, and remounts every component to ensure that effects are properly cleaned up. If you do not clean up your effects, you will experience memory leaks and duplicate network requests during development.

To fix this, always return a cleanup function in your useEffect hooks:

useEffect(() => {
  const controller = new AbortController();
  
  // Fetching data with an abort signal
  fetch('/api/data', { signal: controller.signal })
    .then(res => res.json())
    .then(data => setData(data));

  // Cleanup function to abort the request on unmount
  return () => {
    controller.abort();
  };
}, []);

Common cleanups include clearing intervals, unsubscribing from WebSockets, and removing event listeners.

3. Apply Strict Mode Selectively

If you are working with a large legacy application or using third-party libraries that do not yet support Strict Mode, wrapping your entire application in <React.StrictMode> might cause significant performance bottlenecks or console errors.

Instead of applying it globally, wrap only specific, modern parts of your component tree:

import React from 'react';

function App() {
  return (
    <div>
      <LegacyComponent /> {/* Excluded from Strict Mode */}
      
      <React.StrictMode>
        <NewFeatureComponent /> {/* Protected by Strict Mode */}
      </React.StrictMode>
    </div>
  );
}

This selective approach allows you to optimize new code without being forced to rewrite legacy code immediately.

4. Cache Expensive Calculations

Because Strict Mode runs renders twice, expensive calculations can freeze the UI during development. Use the useMemo and useCallback hooks to cache heavy computations and prevent them from recalculating unnecessarily on the double-render.

// Avoid recalculating on every render pass
const sortedList = useMemo(() => {
  return heavySortAlgorithm(items);
}, [items]);

5. Measure Performance Using Production Builds

If you notice significant lag in your development environment, remember that Strict Mode checks only run in development. To get an accurate assessment of your application’s actual performance, always run performance profiles and audits (like Lighthouse or React DevTools Profiler) using a production build where Strict Mode is automatically stripped out.