Back to Blog
ReactStrictModeDebuggingDevelopment

React StrictMode Guide

Master React StrictMode for catching bugs, deprecations, and preparing for concurrent features.

B
Bootspring Team
Engineering
February 15, 2020
5 min read

StrictMode helps catch bugs and prepare for future React features. Here's how to use it.

Basic Usage#

1import { StrictMode } from 'react'; 2import { createRoot } from 'react-dom/client'; 3import App from './App'; 4 5const root = createRoot(document.getElementById('root')); 6 7root.render( 8 <StrictMode> 9 <App /> 10 </StrictMode> 11); 12 13// Can wrap specific parts 14function App() { 15 return ( 16 <div> 17 <Header /> {/* Not in strict mode */} 18 <StrictMode> 19 <MainContent /> {/* In strict mode */} 20 </StrictMode> 21 <Footer /> {/* Not in strict mode */} 22 </div> 23 ); 24}

What StrictMode Does#

1// 1. Double-invokes certain functions to detect side effects 2 3function Component() { 4 // Called twice in StrictMode during development 5 console.log('Render'); 6 7 const [state, setState] = useState(() => { 8 // Initializer called twice 9 console.log('State initializer'); 10 return 0; 11 }); 12 13 useEffect(() => { 14 // Effect runs, then cleanup runs, then effect runs again 15 console.log('Effect'); 16 return () => console.log('Cleanup'); 17 }, []); 18 19 return <div>{state}</div>; 20} 21 22// Console output in StrictMode: 23// Render 24// Render 25// State initializer 26// State initializer 27// Effect 28// Cleanup 29// Effect

Detecting Impure Renders#

1// BAD: Side effect during render 2let renderCount = 0; 3 4function BadComponent() { 5 renderCount++; // StrictMode will show this doubles 6 return <div>Rendered {renderCount} times</div>; 7} 8 9// GOOD: Use state or refs 10function GoodComponent() { 11 const renderCount = useRef(0); 12 13 useEffect(() => { 14 renderCount.current++; 15 }); 16 17 return <div>Rendered {renderCount.current} times</div>; 18} 19 20// BAD: Mutating objects during render 21function BadMutation({ items }) { 22 items.sort(); // Mutates prop! 23 return <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>; 24} 25 26// GOOD: Create new array 27function GoodMutation({ items }) { 28 const sorted = [...items].sort(); 29 return <ul>{sorted.map(i => <li key={i}>{i}</li>)}</ul>; 30}

Effect Cleanup Testing#

1// StrictMode mounts, unmounts, then remounts components 2// This tests cleanup functions 3 4// BAD: No cleanup 5function BadSubscription() { 6 useEffect(() => { 7 const subscription = dataSource.subscribe(handleData); 8 // Missing cleanup! StrictMode will show duplicate subscriptions 9 }, []); 10 11 return <div>...</div>; 12} 13 14// GOOD: Proper cleanup 15function GoodSubscription() { 16 useEffect(() => { 17 const subscription = dataSource.subscribe(handleData); 18 19 return () => { 20 subscription.unsubscribe(); 21 }; 22 }, []); 23 24 return <div>...</div>; 25} 26 27// BAD: Timer without cleanup 28function BadTimer() { 29 useEffect(() => { 30 setInterval(() => { 31 console.log('tick'); 32 }, 1000); 33 // Memory leak! Multiple intervals in StrictMode 34 }, []); 35 36 return <div>...</div>; 37} 38 39// GOOD: Clear interval 40function GoodTimer() { 41 useEffect(() => { 42 const id = setInterval(() => { 43 console.log('tick'); 44 }, 1000); 45 46 return () => clearInterval(id); 47 }, []); 48 49 return <div>...</div>; 50}

Detecting Deprecated APIs#

1// StrictMode warns about deprecated lifecycle methods 2 3// Warning: componentWillMount is deprecated 4class OldComponent extends React.Component { 5 componentWillMount() { 6 // Deprecated 7 } 8 9 componentWillReceiveProps(nextProps) { 10 // Deprecated 11 } 12 13 componentWillUpdate(nextProps, nextState) { 14 // Deprecated 15 } 16} 17 18// Modern alternatives 19class ModernComponent extends React.Component { 20 static getDerivedStateFromProps(props, state) { 21 // Instead of componentWillReceiveProps 22 return null; 23 } 24 25 getSnapshotBeforeUpdate(prevProps, prevState) { 26 // Instead of componentWillUpdate 27 return null; 28 } 29 30 componentDidUpdate(prevProps, prevState, snapshot) { 31 // Use snapshot here 32 } 33}

Detecting Legacy Context#

1// StrictMode warns about legacy context API 2 3// OLD (deprecated) 4class OldProvider extends React.Component { 5 getChildContext() { 6 return { theme: 'dark' }; 7 } 8 // ... 9} 10 11// NEW 12const ThemeContext = React.createContext('light'); 13 14function NewProvider({ children }) { 15 return ( 16 <ThemeContext.Provider value="dark"> 17 {children} 18 </ThemeContext.Provider> 19 ); 20}

Common Issues and Fixes#

1// Issue: Effect runs twice 2// This is intentional! Fix your effect to handle this 3 4// Issue: API called twice 5function Component() { 6 useEffect(() => { 7 // This will be called twice in development 8 fetchData(); 9 }, []); 10} 11 12// Fix: Use AbortController or ignore stale responses 13function Component() { 14 useEffect(() => { 15 let ignore = false; 16 17 async function fetchData() { 18 const response = await fetch('/api/data'); 19 const data = await response.json(); 20 21 if (!ignore) { 22 setData(data); 23 } 24 } 25 26 fetchData(); 27 28 return () => { 29 ignore = true; 30 }; 31 }, []); 32} 33 34// Or with AbortController 35function Component() { 36 useEffect(() => { 37 const controller = new AbortController(); 38 39 fetch('/api/data', { signal: controller.signal }) 40 .then(res => res.json()) 41 .then(setData) 42 .catch(err => { 43 if (err.name !== 'AbortError') { 44 setError(err); 45 } 46 }); 47 48 return () => controller.abort(); 49 }, []); 50}

State Initialization#

1// StrictMode calls initializers twice 2 3// BAD: Side effect in initializer 4function BadInitializer() { 5 const [data] = useState(() => { 6 // This runs twice! 7 localStorage.setItem('initialized', 'true'); 8 return loadFromStorage(); 9 }); 10} 11 12// GOOD: Pure initializer 13function GoodInitializer() { 14 const [data, setData] = useState(() => { 15 // Just read, don't write 16 return localStorage.getItem('data') || defaultData; 17 }); 18 19 useEffect(() => { 20 // Side effects in useEffect 21 localStorage.setItem('initialized', 'true'); 22 }, []); 23} 24 25// BAD: Mutating during initialization 26function BadMutation() { 27 const [items] = useState(() => { 28 const cached = cache.get('items'); 29 cached.lastAccessed = Date.now(); // Mutation! 30 return cached; 31 }); 32} 33 34// GOOD: Return new object 35function GoodInit() { 36 const [items] = useState(() => { 37 const cached = cache.get('items'); 38 return { 39 ...cached, 40 lastAccessed: Date.now(), 41 }; 42 }); 43}

Production Behavior#

1// StrictMode only affects development 2 3// In development: 4// - Double renders 5// - Double effect invocations 6// - Deprecation warnings 7 8// In production: 9// - No double rendering 10// - Normal effect behavior 11// - No warnings 12 13// StrictMode has no runtime cost in production 14// It's stripped out during build

Best Practices#

Enable StrictMode: ✓ Wrap entire app for new projects ✓ Enable for new features ✓ Address warnings before shipping ✓ Use for migration to newer React Fix Common Issues: ✓ Add cleanup to all effects ✓ Make renders pure ✓ Use AbortController for fetches ✓ Avoid side effects in initializers Testing: ✓ Test that effects handle remounting ✓ Verify subscriptions clean up ✓ Check for memory leaks ✓ Test with concurrent features Avoid: ✗ Disabling to hide warnings ✗ Side effects during render ✗ Relying on effect timing ✗ Ignoring deprecation warnings

Conclusion#

StrictMode is essential for catching bugs and preparing for React's concurrent features. The double-invoking behavior helps find missing cleanups and impure renders. Always enable StrictMode during development and address all warnings before production.

Share this article

Help spread the word about Bootspring