Search code examples
reactjsreact-hookssocket.iosetinterval

React useState is not updating when set method is called in an interval


I have a websocket server that sends an object containing some hashes every 15 seconds. When the client receives a hash, I want to check with my current hash. If they differ, I want to make a call to an API to fetch new data.

The socket is working and sending the hash correctly. If the data updates on the server I get a different hash. My problem is that the hash variable I use to store the current hash is not updated correctly.

I have disabled the socket listening in my component, just to make sure that that is not the problem. Instead I have added a setInterval to mimik the socket update.

This is my code (socked code disabled but left as a comment):

import { useCallback, useEffect, useState } from "react";
import { useAuth, useSocket } from "../utils/hooks";

const Admin = () => {
    const [ questionLists, setQuestionLists ] = useState<QuestionListModel[]>([]); 
    const { user } = useAuth();
    const { socket } = useSocket();
    const [ hash, setHash ] = useState<Hash>({questionList: ""});

    const fetchHash = useCallback(async () => {
        setHash({questionList: "sdhfubvwuedfhvfeuvyqhwvfeuq"});
    }, []);

    const fetchQuestionLists = useCallback(async () => {
        console.log("fetching new question lists");
        const response: ApiResponse | boolean = await getQuestionLists(user?.token);

        if (typeof response !== "boolean" && response.data) {
            setQuestionLists(response.data);
        }
    }, [hash]);

    useEffect(() => {    
        fetchHash();
        fetchQuestionLists();
    }, []);

    const update = useCallback((newHash: Hash) => {
        console.log("called update");

        let shouldUpdate = false;
        let originalHash = { ...hash };
        let updatedHash = { ...newHash };

        console.log("new:    ", newHash);
        console.log("stored: ", originalHash);

        if (hash.questionList !== newHash.questionList) {
            console.log("was not equal");
            updatedHash = { ...updatedHash, questionList: newHash.questionList} 
            shouldUpdate = true;
        }

        if (shouldUpdate) {
            console.log("trying to set new hash: ", updatedHash);
            setHash(updatedHash);
            fetchQuestionLists();
        }
    }, [hash]);

    /*useEffect(() => {    
        socket?.on('aHash', (fetchedHash) => update(fetchedHash));
    }, []);*/

    useEffect(() => {    
        setInterval(() => {
            update({questionList: "sdhfubvwuedfhvfeuvyqhwvfeuq"});
        }, 15000)
    }, []);

    return (
        <>
            ... Things here later ...
        </>
    );
};

export default Admin;

After the initial render, and waiting two interval cycles, this is what I see in the console:

fetching new question lists
called update
new:  {questionList: 'sdhfubvwuedfhvfeuvyqhwvfeuq'}
stored:  {questionList: ''}
was not equal
trying to set new hash:  {questionList: 'sdhfubvwuedfhvfeuvyqhwvfeuq'}
fetching new question lists
called update
new:  {questionList: 'sdhfubvwuedfhvfeuvyqhwvfeuq'}
stored:  {questionList: ''}
was not equal
trying to set new hash:  {questionList: 'sdhfubvwuedfhvfeuvyqhwvfeuq'}
fetching new question lists

You can see that stored is empty. That leads me to believe that setHash(updatedHash); never runs for some reason. Why is that?


Solution

  • Having hacked about with this in codepen here: https://codesandbox.io/s/as-prop-base-forked-l3ncvo?file=/src/Application.tsx

    This seems to me to be a closure issue as opposed to a React issue. If you have a look in the dev tools, you'll see the state of the component is doing what you're expecting it to. The issue is that the console log is not.

    useEffect is only ever going to use an old version of update, so the console won't log what you're expecting. If you add update to the dependency array (and add a clean up so we don't end up with tonnes of intervals) you'll get what you're looking for. Can be seen in the linked codepen.