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?
CodePudding user response:
I think the issue in on this line :
socket?.on('aHash', (hash) => update(hash));
maybe when you register a listener, it keeps the first value of update only,
can you please share useSocket?
CodePudding user response:
const [ hash, setHash ] = useState<Hash>({questionList: ""});
const fetchHash = useCallback(async () => {
setHash({questionList: "sdhfubvwuedfhvfeuvyqhwvfeuq"});
}, []);
Include setHash
in your dependency list et voilà
EDIT: Or well, you should include these dependencies in all your useCallback/useEffect hooks since the reference will be lost whenever the component updates. You always have to include all dependencies in the dependency list not to get unpredictable behavior.
CodePudding user response:
use setState(prevValue => {}) to get the the preferred effect. Also, if you running in a Strict mode this will fire the setState twice.
Here is how the code should look like:
import { useCallback, useEffect, useState } from "react";
import { faker } from '@faker-js/faker';
const Admin = () => {
const [ questionLists, setQuestionLists ] = useState([]);
const [ hash, setHash ] = useState({questionList: ""});
const fetchHash = useCallback(async () => {
setHash({questionList: "sdhfubvwuedfhvfeuvyqhwvfeuq"});
}, []);
const fetchQuestionLists = useCallback(async () => {
console.log("fetching new question lists");
const response = {data: {hash: 'asdf-1234'}}
setQuestionLists(response.data);
}, [hash]);
useEffect(() => {
fetchHash();
fetchQuestionLists();
}, []);
const update = (newHash) => {
console.log("called update");
setHash(oldHash => {
console.log('old hash: ', oldHash);
console.log('new hash', newHash);
if (JSON.stringify(oldHash) !== JSON.stringify(newHash)) {
return newHash
}
})
};
/*useEffect(() => {
socket?.on('aHash', (fetchedHash) => update(fetchedHash));
}, []);*/
useEffect(() => {
setInterval(() => {
update({questionList: faker.random.numeric(36)});
}, 15000)
}, []);
return (
<>
<h2>Hash</h2>
{JSON.stringify(hash)}
</>
);
};
export default Admin;
CodePudding user response:
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.