I'm simulating a scenario of opening packs of cards. I have a UI that shows the amount of packs a user has. If the user has many packs, I have a function to open all the packs in a repetitive, quick motion. If the user chooses to 'open all', the user can see the contents of the pack for 1100ms, until the next pack open is rendered. Otherwise they would have to open all the packs individually, which could be time consuming.
After each pack open, I set the state of the UI to show the content of the pack and the amount of packs the user has. The content of the pack renders every time, however, the counter doesn't. The counter updates at the very end. I'm curious as to why the data updates every iteration, but the counter doesn't.
I toggle openAllMode:
function openAll() {
setOpenAllMode(!openAllMode);
}
Then I useEffect:
useEffect(() => {
if (openAllMode) {
Promise.all(getDataAsPromises()).then(() =>
setTimeout(() => getUserPackCount(), 1100)
);
}
}, [openAllMode]);
function getDataAsPromises() {
return Array(packCount)
.fill(null)
.map((c, i) =>
new Promise((resolve) => setTimeout(resolve, i * 1100)).then(() =>
getData()
)
);
}
function getData() {
getRandomData(1)
.then((data) => {
setData(data);
setPackCount(packCount - 1);
})
.then(
() => {
console.log("Opened Pack!");
setOpen(true);
},
(error) => {
console.error("Error Opening Pack: " + error);
setOpen(false);
}
);
}
Basically, every 1100ms, the state of 'setData' is re-rendering. However, the state of 'setPackCount' isn't re-rendering until the end.
You've a stale enclosure over the packCount
state. The issue is that packCount
is closed over in getDataAsPromises
callback scope and each setPackCount(packCount - 1);
is just overwriting the previous state update within the loop.
Use a functional state update to correctly reference any previous state.
setPackCount(packCount => packCount - 1);