Updating an object with useState isn't updating

38 views Asked by At

I have a React context that is going to hold a huge list of filenames in an object for me. Each array of filenames will be associated with a given camId.

So, when hydrated, the filenamesObj should look like this:

{
cam1: [file1, file2, file3]
cam2: [filea, fileb, filec]
//    etc...
}

This is the code for the context and provider:

export const FilenamesContext = createContext()

export const FilenamesProvider = ({ children }) => {
    const [filenamesObj, setFilenamesObj] = useState({})

    const addFilenames = ({ camId, incomingFilenamesArr }) => {
        let newFilenamesArr
        if (filenamesObj[camId]) {
            newFilenamesArr = combineAndSortArrays(filenamesObj[camId], incomingFilenamesArr)
        } else {
            newFilenamesArr = incomingFilenamesArr
        }
        console.log('newFilenamesArr :>> ', newFilenamesArr); // THIS PRINTS THE CORRECT VALUE

        setFilenamesObj({ ...filenamesObj, [camId]: newFilenamesArr })
    }

    return (
        <FilenamesContext.Provider
            value={{
                filenamesObj,
                addFilenames,
            }}
        >
            {children}
        </FilenamesContext.Provider>
    )
}

I then bring this into a react component, it effectively just shows the filenames - it's a proof-of-concept:

export default function CameraDetails() {
    let { camId } = useParams()
    let { filenamesObj } = useFilenames()

    return (
        <>
            <FilenamesLoader camId={camId} />
            <Typography>Cam file names {camId}</Typography>

            {JSON.stringify(filenamesObj)}

            <Link to={"/"}>Go HOME</Link>
        </>
    )
}

Note, the filenamesLoader component is supposed to make an API call and load the results in to the FilenamesContext by way of the addFilenames function (defined above):

export default function CameraLoader({ camId }) {
    const { filenamesObj, addFilenames } = useContext(FilenamesContext)

    useEffect(() => {
        async function getAllFilenames(camId, before, count = 1) {
            if (count > 5) {
                console.log("count reached 5") // keeps this from being an infinite loop
                return
            }
            let returnedFilenames = await api.getFilenames({ camId, before })
            addFilenames({ camId, incomingFilenamesArr: returnedFilenames })

            await getAllFilenames(camId, getDateFromFilename(returnedFilenames[returnedFilenames.length - 1]), count + 1)
        }

        getAllFilenames(camId)
    }, [])

    return null
    }

However, the problem: is that the filenamesObj stored in the context never seems to remember the value... It shows the value of the first call... the updates to show the value of the second call... then the third... in the end, it always just shows the value of the 5th API call.... not the combination of the 5 separate calls.

My conjecture is that the filenamesObj in the context is not being updated (or I'm working with a 'stale' value of it) and thus the setFilenamesObj({ ...filenamesObj, [camId]: newFilenamesArr }) is really just 'spreading' an empty object rather than an object with values.

Help?

1

There are 1 answers

0
lowcrawler On

The issue was that by calling the function recursively, it was being called with a stale state.

I changed the addFilenames function to a functional call using 'prevState'. The new, working, function looks like this:

const addFilenames = ({ camId, incomingFilenamesArr }) => {
        setFilenamesObj((prevFilenamesObj) => {
            let newFilenamesArr
            if (prevFilenamesObj[camId]) {
                newFilenamesArr = combineAndSortArrays(prevFilenamesObj[camId], incomingFilenamesArr)
            } else {
                newFilenamesArr = incomingFilenamesArr
            }

            return { ...prevFilenamesObj, [camId]: newFilenamesArr }
        })
    }

Thanks to @G Gallegos answer here: Why React useState with functional update form is needed? for getting me to the right place