I have been reading different things about useEffect, useState and rerendering, but I cannot understand the issue here. The data update itself works fine but for some reason, I need to refresh the page to get the updated content.
I have noticed a common issue with arrays in my research is when React doesn't understand data has changed and keeps the same reference, hence the setGames([...updatedGames])
but to no avail.
const getStaticProps: GetStaticProps = async () => {
const gamesRepo: Game[] = repo.getAll()
return { props: { gamesRepo } }
}
const Home: NextPage = ({ gamesRepo }: InferGetStaticPropsType<typeof getStaticProps>) => {
const [games, setGames] = useState(gamesRepo)
useEffect(() => {
let gamesIdToUpdate: number[] = []
// ... filtering the ids to update here ...
if (gamesIdToUpdate.length > 0) {
const endpoint = '/api/games/update'
const options = { method: 'POST', //... }
fetch(endpoint, options)
.then(res => res.json())
.then((updatedGames: Game[]) => {
setGames([...updatedGames])
})
}
}, [games])
return (
<>
<div className={styles.container}>
games.map((game: Game) => (
<GameCard key={game.id} gameData={game} />
))
<AddGameCard></AddGameCard>
</div>
</>
);
There were different problems, some were indeed not shown in the excerpts I shared. To make this thread useful for future readings:
export const getStaticProps: GetStaticProps = () => {
// getting the data from a JSON file
const gamesRepo: Game[] = repo.getAll()
return { props: { gamesRepo } }
}
const Home: NextPage = ({ gamesRepo }: InferGetStaticPropsType<typeof getStaticProps>) => {
const [games, setGames] = useState(gamesRepo)
useEffect(() => {
// checking if the data is 3 days old or more
const today = new Date().getDate()
let gamesIdToUpdate: number[] = []
for (const game of games) {
const lastUpdated = new Date(game.dateUpdated).getDate()
const dateDiff = today - lastUpdated
if (dateDiff >= 3) {
gamesIdToUpdate.push(game.id)
}
}
// actually updating the data that is 3 days old or more
if (gamesIdToUpdate.length > 0) {
const endpoint = '/api/games/update'
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(gamesIdToUpdate),
}
fetch(endpoint, options)
.then(res => res.json())
.then((updatedGames: Game[]) => {
setGames([...updatedGames])
})
}
}, [])
return (
<>
<div className={styles.container}>
{
games.length > 0 &&
games.map((game: Game) => (
<GameCard key={game.id} gameData={game} />
))
}
</div>
</>
);
Some noticed the useEffect(..., [games])
in my original post that was indeed causing multiple calls to useEffect even though I just needed it once.
The problem wasn't in this component, the setGames
state was working fine all along! It was in the GameCard child component. Here it is:
export const GameCard = (props: { gameData: Game }) => {
const [game, setGame] = useState({...props.gameData})
// I didn't have useEffect before in this component, hence it was never triggering a re-render !
useEffect(() => {
setGame(props.gameData)
}, [props.gameData])
// some other methods here
...
return (
<div className="flex justify-center">
<div className="w-64 my-10 outline outline-2 shadow-md shadow-deep-blue">
{ game.name }
</div>
</div>
}
Since I am using the state variable game
in my component's template and for the other methods in this component instead of the props directly, I mixed up both concepts (props and state variables) and was expecting the component to re-render automatically upon change, which it clearly couldn't since inner state changes are not tracked automatically by React.