Skip to content

Instantly share code, notes, and snippets.

@sourman
Created January 26, 2025 17:56
Show Gist options
  • Save sourman/8afdde660550b4e67191ecbb45690d76 to your computer and use it in GitHub Desktop.
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
/** 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