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// EffectDetecting 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 buildBest 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.