skip to Main Content

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


  1. You should use a useRef for your sentStatus.

        function GameRound() {
    
        // ...
        const [timerTime, setTimerTime] = useState(30);
        const sentStatusRef = useRef(false)
        // ...
    
        let timerInterval;
        useEffect(() => {
            const decTimer = () => {
                setTimerTime((oldTimerTime) => {
                    if(oldTimerTime === 1) {    // this statement is there because using "timerTime" outside the
                        if(!sentStatusRef.current)             // 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
    
            // ...
    
            sentStatusRef.current = true;
        }
    
        return (
            <>
                {/* ... */}
                <div onClick={sendAnswer}>send answer</div>
                {/* ... */}
            </>
        )
    
    }
    

    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)

    Login or Signup to reply.
  2. The useEffect dependency is missing the sent state.

    Therefore, the sent variable in the decTimer function is always false, even if you change sent to true in the sendAnswer function.

    To solve this problem, you should use useRef instead of useState.

    
    // original: const [sent, setSent] = useState(true)
    const sentRef = useRef(false);
    
    //...
    
    const sendAnswer = async () => {
        fetch("......", {/*...*/}); // send the answer to the server
    
        // ...
    
        sentRef.current = true;
    }
    
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search