reactjsnext.js

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

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