How to build a responsive timer for a multiplayer game

27 views Asked by At

I'm building a multiplayer trivia game with React and I've been struggling with building a timer component. The goal would be to implement a time limit per round.

This is the logic behind the feature: https://imgur.com/oJtl98Y

I've copied an implementation online, adding a useEffect to fit my needs. It works by extracting roundDuration from my payload context, then it uses the state variable parsedDeadline to obtain the currentTime by substracting parsedDeadline to Date.now(). I use the special value "-1" to disable the Timer (in case the host sets unlimited time in the game settings).

when 'start_game' or 'start_round' event is caught, the timer is reset to the deadline specified inside the payload (roundDuration is the time limit in milliseconds e.g. 10000).

Timer.tsx

const SECOND = 1000;
const MINUTE = SECOND * 60;
const HOUR = MINUTE * 60;
const DAY = HOUR * 24;

interface TimerProps {
    isAnswerDisabled:boolean;
}

export const Timer:React.FC<TimerProps> = ({isAnswerDisabled}) => {
    const payload = usePayload()

    const info = useGameInfo();
    const setInfo = useGameInfoSetter()!;

    const parsedDeadline = useMemo(() => new Date(new Date().getTime() + payload.metadata.roundDuration).getDate(), [payload.metadata.roundDuration]);
    const [time, setTime] = useState(parsedDeadline - Date.now());

    useEffect(() => {


        if (time <= 0 && !isAnswerDisabled) {

            socket.emit("submit_answer", { room: info.roomId, answer:"" })
            setInfo({
                ...info,
                roundDuration: -1
            })
            return; //timer has reached or passed the deadline, do not update
        }

        const interval = setInterval(
            () => {
                const currentTime = parsedDeadline - Date.now();
                console.log("currentTime",currentTime)
                setTime(currentTime > 0 ? currentTime : 0)
            },
            1000,
        );

        return () => clearInterval(interval);
    }, [parsedDeadline]);

    return (
        <div className="timer">
            {Object.entries({
                Minutes: (time / MINUTE) % 60,
                Seconds: (time / SECOND) % 60,
            }).map(([label, value]) => (
                <div key={label} className="col-4">
                    <div className="box">
                        <p>{`${Math.floor(value)}`.padStart(2, "0")}</p>
                        <span className="text">{label}</span>
                    </div>
                </div>
            ))}
        </div>
    );
};

Father Component

{
    payload.metadata.isRoundOnGoing
    &&
    payload.metadata.roundDuration !== -1
    && 
    info.roundDuration !== -1
    &&
    <div className='timer'> 
      <Timer isAnswerDisabled={isAnswerDisabled} />
    </div>
  }

the problem is that I really don't know how to despawn and respawn this component without breaking the count inside it. I'm pretty new to React and this type of event-driven development so I'm sorry if the solution is simple.

What strategy/pattern should I use to make sure this code above is robust and works correctly?

0

There are 0 answers