I have two useEffect hooks in a component (InternalComponent) that displays a single data item. One useEffect tracks a count as state, POSTing to a database when the user increments the count. The second useEffect resets the count when the item tracked by the InternalComponent changes (due to external manipulation).
The problem is: the useEffect that updates the database will fire when the item changes; it will see the new item value, but will incorrectly send the database the count from the previous item. This occurs because the two useEffects fire "simultaneously", with the state that would indicate the count shouldn't be POSTed not being set until after the POST useEffect is run.
const InternalComponent = ({ item }) => {
const [count, setCount] = useState(item.count);
const [countChanged, setCountChanged] = useState(false);
useEffect(() => {
console.log(
`Item is now ${item.id}; setting count from item and marking unchanged.`
);
setCount(item.count);
setCountChanged(false);
}, [item]);
useEffect(() => {
if (countChanged) {
console.log(
`Count changed for item ${item.id}, POSTing (id=${item.id}, count=${count}) to database.`
);
} else {
console.log(
`Count hasn't changed yet, so don't update the DB (id=${item.id}, count=${count})`
);
}
}, [item, count, countChanged]);
const handleButtonClick = () => {
setCount(count + 1);
setCountChanged(true);
};
return (
<div>
I'm showing item {item.id}, which has count {count}.<br />
<button onClick={handleButtonClick}>Increment item count</button>
</div>
);
};
Minimal working example on Code Sandbox: https://codesandbox.io/s/post-with-stale-data-vfzh4j?file=/src/App.js
The annotated output:
1 (After button click) Count changed for item 1, POSTing (id=1, count=6) to database.
2 (After item changed) Item is now 2; setting count from item and marking unchanged.
3 Count changed for item 2, POSTing (id=2, count=6) to database.
4 (useEffect runs twice) Count hasn't changed yet, so don't update the DB (id=2, count=50)
Line 3 is the unwanted behavior: the database will receive a POST with the wrong item ID and that ideally shouldn't have been sent at all.
This feels like a simple/common problem: what design am I supposed to use to prevent the POST useEffect from firing with stale state? All the solutions I can easily think of seem absurd to me. (e.g. creating one InternalComponent for each item and only displaying one of them, combining all the useEffects into a single giant useEffect that tracks every state in the component, etc.) I'm sure I'm overlooking something obvious: any ideas? Thank you!