Reputation: 134
In a React project, I have a state gameResults
with a array of games, and I have a function to get the list of games based on a query :
useEffect(() => {
const timeoutId = setTimeout(() => {
if (gameQuery.length > 0) {
axios.get(`/api/games/${gameQuery}`).then((response) => {
const igdbGames: IGDBGame[] = response.data.games;
const formatedGames = formatGames(igdbGames);
setGameResults(formatedGames);
});
}
}, 300);
return () => clearTimeout(timeoutId);
}, [gameQuery]);
For each game, I don't have the cover, so I get the cover for each game :
const loadGamesImages = async () => {
for (let i = 0; i < gameResults.length; i++) {
axios
.get(`/api/cover/${gameResults[i].id}`)
.then((response) => {
const coverUrl: IGDBCover = response.data.covers[0];
const newGame = {
...gameResults[i],
cover: coverUrl.url.replace("//", "https://"),
};
const newGames = gameResults.filter(
(game: Game) => game.id !== newGame.id
);
setGameResults([...newGames, newGame]);
})
.catch((error) => {
console.log("error", error);
});
await sleep(300);
}
console.log("finish");
};
useEffect(() => {
loadGamesImages();
}, [gameResults.length]);
Here is my problem : when React update the state, the old state is not there anymore. I explain : for the first cover, it's ok the new state has the first game covered. But when he make a new state for the second game, as you can see i get the gameResults
state, but in this one the first game has no cover anymore.
What have I done wrong ?
Upvotes: 0
Views: 48
Reputation: 371138
Each one of your looped asynchronous calls closes over the initial binding of the stateful gameResults
- and gameResults
starts out empty. For example, with the first Promise that resolves, these line:
const newGames = gameResults.filter(
(game: Game) => game.id !== newGame.id
);
setGameResults([...newGames, newGame]);
have the gameResults
refer to the empty array, so setGameResults
properly spreads the empty array plus the just-added newGame
.
But then on further Promise resolutions, they also close over the initially-empty gameResults
- all the async calls happened before the component re-rendered.
Use a callback instead, so that the async calls don't overwrite each other:
setGameResults((gameResults) => {
const newGames = gameResults.filter(
(game) => game.id !== newGame.id
);
return [...newGames, newGame];
});
(also note that there's no need to explicitly note the type of a parameter that TS can already infer automatically: (game: Game)
can be just game
)
Once this is working, I'd also suggest tweaking your code so that, when the effect hook runs again, only covers that have not been retrieved yet get requested again. This'll save you from unnecessarily making duplicate requests.
Upvotes: 1