I am developing a small multiplayer web game, and after completing the backend, I’ve turned my attention to the challenging frontend in React.
To summarize, it’s similar to popular games like "Gartic Phone" or "Skribbl", where players connect to a lobby and start a game with their friends. Each game consists of multiple rounds, each with a maximum time limit for each player.
Currently, I’m working on implementing the time limit functionality for each round, and I’m facing issues with sending the player’s input if they fail to press the send button within the allocated time.
Let me show you the current situation:
function GameRound() {
// ...
const [timerTime, setTimerTime] = useState(30);
const [sent, setSent] = useState(true)
// ...
let timerInterval;
useEffect(() => {
const decTimer = () => {
setTimerTime((oldTimerTime) => {
if(oldTimerTime === 1) { // this statement is there because using "timerTime" outside the
if(!sent) // set function would not use the correct "timerTime" current value
sendAnswer()
}
return oldTimerTime-1
});
};
// ...
sharedEventSource.addEventListener("game", (event) => {
// ...
timerInterval = setInterval(decTimer, 1000);
// ...
})
// ...
}, [sharedEventSource]);
const sendAnswer = async () => {
fetch("......", {/*...*/}); // send the answer to the server
// ...
setSent((oldSent) => {return true})
}
return (
<>
{/* ... */}
<div onClick={sendAnswer}>send answer</div>
{/* ... */}
</>
)
}
I have implemented the send answer button in the UI that allows players to submit their response to a question to the server before the timer expires. This triggers the sendAnswer function setting the "sent" state to true. Within the useEffect, there is an interval callback named "decTimer", which is initiated when the server sends the SSE event "game".
The purpose of "decTimer" is to decrement the "timerTime" value by 1 every second. If it reaches 0, the player’s response is sent to the server.
Note the "if(!sent)": I’ve included this condition to prevent "sendAnswer" from being called if the player has already submitted their answer by clicking the button.
What’s the problem?
Upon reaching 0, the timer triggers the "if(!sent)" condition. However, regardless of the "setSent" call inside the "sendAnswer" function, The "sendAnswer" function is invoked again by the interval, even if the player has already submitted their answer. (Additionally, when the interval calls "sendAnswer", the answer sent through the fetch request is just the default value set in the useState initial setup. However, when players trigger the function by pressing the button, the answer is sent correctly to the server.)
I suspect this issue arises because the "sendAnswer" callback is declared outside the useEffect, but I’m struggling to find a solution!
2
Answers
You should use a useRef for your sentStatus.
And I think if I had this problem. it’s the backend that will put a random answer if someone didn’t vote. because I find it a little weird to have the frontend that will trigger a useEffect for this issue. (Open to dicussion)
The
useEffect
dependency is missing thesent
state.Therefore, the
sent
variable in thedecTimer
function is always false, even if you changesent
to true in thesendAnswer
function.To solve this problem, you should use useRef instead of useState.