'NextJS component not rerendering upon update

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>
        </>
    );



Solution 1:[1]

First of all, thanks to @Damian Busz and @Brian Thomson for helping out here. I figured the solution to my problem and understood quite a bit in the way.

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>
        </>
    );
  1. Some noticed the useEffect(..., [games]) in my original post that was indeed causing multiple calls to useEffect even though I just needed it once.

  2. 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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 fassn