State not immediately saved with socket.io

35 Views Asked by At

As you can see in my problem below, I receive a message with the action stop. There I update 3 state variables. My problem is with the setTotalScore. totalScore is 0 in the beginning, and when I receive the message, I update it to be the same as the user.score. But, if I receive the message again, it is 0 again, even though it shouldn't be. I have read everywhere that it is a problem with how useState updates. I don't really know how to work around that. Here is the code:

socket.on(`${WsTopic.GAME}/${WsAction.STOP}`, (payload) => {
  const user = payload.find((entry: any) => entry.username == name)
  if (user) {
    console.log("USER: ", user," USER.SCORE: ", user.score);
    console.log("SCORE: ", totalScore);

    const addedScore = user.score - totalScore;

    setTotalScore(user.score);
    setAddedScore(addedScore);
    setShowAnswer(true);
  }
})
const [totalScore, setTotalScore] = useState<number>(0);

The totalScore should always be updated and equal to the user.score. You can look at it like the totalScore is the prevScore of the user.score.

1

There are 1 best solutions below

0
Drew Reese On

This is a Javascript closure issue, the socket closes over the totalScore state value from the render cycle where the socket event handler is instantiated and handed off to the socket.

You can use a useEffect with proper dependencies to re-enclose variables:

React.useEffect(() => {
  const stopHandler = (payload) => {
    const user = payload.find((entry: any) => entry.username == name);
    if (user) {
      console.log("USER: ", user," USER.SCORE: ", user.score);
      console.log("SCORE: ", totalScore);

      const addedScore = user.score - totalScore;

      setTotalScore(user.score);
      setAddedScore(addedScore);
      setShowAnswer(true);
    }
  };

  socket.on(`${WsTopic.GAME}/${WsAction.STOP}`, stopHandler);

  return () => {
    socket.off(`${WsTopic.GAME}/${WsAction.STOP}`, stopHandler);
  };
}, [totalScore]); 

You could also use a React ref to cache the current totalScore state value and reference this in the handler:

const [totalScore, setTotalScore] = useState<number>(0);
const totalScoreRef = React.useRef(totalScore);

React.useEffect(() => {
  totalScoreRef.current = totalScore;
}, [totalScore]);

React.useEffect(() => {
  const stopHandler = (payload) => {
    const user = payload.find((entry: any) => entry.username == name);
    if (user) {
      console.log("USER: ", user," USER.SCORE: ", user.score);
      console.log("SCORE: ", totalScoreRef.current);

      const addedScore = user.score - totalScoreRef.current;

      setTotalScore(user.score);
      setAddedScore(addedScore);
      setShowAnswer(true);
    }
  };

  socket.on(`${WsTopic.GAME}/${WsAction.STOP}`, stopHandler);

  return () => {
    socket.off(`${WsTopic.GAME}/${WsAction.STOP}`, stopHandler);
  };
}, []);