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:
CodePudding user response:
Well, you are not updating the items
with it's own setter function, instead put it in a count
state, which makes the logic all over the place, pretty hard to read, to be honest, I am here to propose a different solution that might solve your problem:
const InternalComponent = ({ items, setItems, index }) => {
useEffect(() => {
console.log("item count changed: ", items[index].count);
}, [items, index]);
const handleButtonClick = () => {
setItems(
items.map((item) =>
item.id - 1 === index ? { ...item, count: item.count 1 } : { ...item }
)
);
};
return (
<div>
I'm showing item {items[index].id}, which has count {items[index].count}.
<br />
<button onClick={handleButtonClick}>Increment item count</button>
</div>
);
};
This approach keeps the updated state, if you want to default back to it's original count
value after clicking next item
, it should be easy to do that.
Let me know if this way solves the problem