Created
January 26, 2025 17:56
-
-
Save sourman/8afdde660550b4e67191ecbb45690d76 to your computer and use it in GitHub Desktop.
React Example of a state update the repeats too many times because it is being called INSIDE another state update
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** Examples showing incorrect state update the violates Reacts rules because it nests | |
* a setSomething() call inside of setOhterThing() call. This casues the state update | |
* of setSomthing() to occur multiple times in a row for every call to setOtherThing() | |
*/ | |
/************************************************************************************/ | |
/*************************** BAD STATE UPDATE EXAMPLE COMPONENT *********************/ | |
/************************************************************************************/ | |
const badLog = (...args: any[]) => { | |
console.log('❌BadComponent❌ Says: ', ...args) | |
} | |
import React, { useState, useEffect } from 'react'; | |
export function BadComponent() { | |
const [count, setCount] = useState(0); | |
const [history, setHistory] = useState<number[]>([]); | |
// Log state changes | |
useEffect(() => { | |
badLog('📊 Count changed:', count); | |
}, [count]); | |
useEffect(() => { | |
badLog('📜 History changed:', history); | |
}, [history]); | |
// ❌ Bad: Updating history state inside count's state updater | |
const handleClick = () => { | |
badLog('🖱️ Click handler started'); | |
setCount(prev => { | |
badLog('🔄 Count updater running, current value:', prev); | |
// This will cause issues because we're updating history | |
// inside another state update function | |
setHistory(prevHistory => { | |
badLog('📝 History updater running inside count updater'); | |
return [...prevHistory, prev]; | |
}); | |
setHistory(prevHistory => { | |
badLog('📝 History updater running inside count updater'); | |
return [...prevHistory, prev]; | |
}); | |
return prev + 1; | |
}); | |
badLog('🖱️ Click handler finished'); | |
}; | |
return ( | |
<div className="p-4 border rounded"> | |
<h2 className="text-lg font-bold">Bad Component</h2> | |
<p>Count: {count}</p> | |
<p>History: {history.join(', ')}</p> | |
<button | |
onClick={handleClick} | |
className="px-4 py-2 bg-red-500 text-white rounded" | |
> | |
Increment | |
</button> | |
</div> | |
); | |
}; | |
/************************************************************************************/ | |
/************************** GOOD STATE UPDATE EXAMPLE COMPONENT *********************/ | |
/************************************************************************************/ | |
export const goodLog = (...args: any[]) => { | |
console.log('✅GoodCompnent✅ Says: ', ...args); | |
} | |
export function GoodComponent() { | |
const [count, setCount] = useState(0); | |
const [history, setHistory] = useState<number[]>([]); | |
// Log state changes | |
useEffect(() => { | |
goodLog('📊 Count changed:', count); | |
}, [count]); | |
useEffect(() => { | |
goodLog('📜 History changed:', history); | |
}, [history]); | |
// ✅ Good: Calculate state changes first, then update all states in a single transition | |
const handleClick = () => { | |
goodLog('🖱️ Click handler started'); | |
// Calculate new states outside of setState | |
const newCount = count + 1; | |
const newHistory = [...history, count]; | |
goodLog('🧮 Calculated new states:', { newCount, newHistory }); | |
// Batch updates together in a transition | |
// React.startTransition(() => { | |
goodLog('🎭 Starting transition'); | |
setCount(newCount); | |
setHistory(newHistory); | |
goodLog('✅ State updates queued'); | |
//}); | |
goodLog('🖱️ Click handler finished'); | |
}; | |
// Alternative approach using useReducer if states are tightly coupled | |
/* | |
const [state, dispatch] = useReducer( | |
(state: { count: number; history: number[] }, action: { type: 'INCREMENT' }) => { | |
switch (action.type) { | |
case 'INCREMENT': | |
return { | |
count: state.count + 1, | |
history: [...state.history, state.count] | |
}; | |
default: | |
return state; | |
} | |
}, | |
{ count: 0, history: [] } | |
); | |
*/ | |
return ( | |
<div className="p-4 border rounded"> | |
<h2 className="text-lg font-bold">Good Component</h2> | |
<p>Count: {count}</p> | |
<p>History: {history.join(', ')}</p> | |
<button | |
onClick={handleClick} | |
className="px-4 py-2 bg-green-500 text-white rounded" | |
> | |
Increment | |
</button> | |
</div> | |
); | |
}; | |
export default function App() { | |
return ( | |
<div className='App'> | |
<BadComponent /> | |
<GoodComponent /> | |
</div> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment