From f09bdd1823d166dced39d1d6a529edce12a0fccb Mon Sep 17 00:00:00 2001 From: Kartikeya Nainkhwal Date: Thu, 9 Oct 2025 16:00:28 +0530 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=9A=80=20Refactor:=20Move=20features?= =?UTF-8?q?=20to=20individual=20controller=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ Major code reorganization for better maintainability: 📁 NEW CONTROLLERS CREATED: • GameController.js - Handles game logic, cards, turns, matching • TimerController.js - Manages time tracking and elapsed time • AudioController.js - Centralizes all sound effects management • ScoreController.js - Handles scoring, high scores, game completion • HintController.js - Manages hint system and cooldowns • ErrorController.js - Provides error handling and logging • index.js - Clean exports for easy importing 🔧 IMPROVEMENTS: • Reduced App.js complexity from 323+ lines • Separated concerns for better code organization • Enhanced maintainability and readability • Easier testing and debugging • Prepared for future feature additions • Following React best practices 🎮 FUNCTIONALITY: • All existing features work exactly the same • No breaking changes for users • Improved developer experience • Ready for Hacktoberfest contributions This refactoring makes the codebase more modular and easier for contributors to understand and extend. --- src/App.js | 308 +++++++++-------------------- src/controllers/AudioController.js | 32 +++ src/controllers/ErrorController.js | 31 +++ src/controllers/GameController.js | 99 ++++++++++ src/controllers/HintController.js | 120 +++++++++++ src/controllers/ScoreController.js | 54 +++++ src/controllers/TimerController.js | 39 ++++ src/controllers/index.js | 7 + 8 files changed, 470 insertions(+), 220 deletions(-) create mode 100644 src/controllers/AudioController.js create mode 100644 src/controllers/ErrorController.js create mode 100644 src/controllers/GameController.js create mode 100644 src/controllers/HintController.js create mode 100644 src/controllers/ScoreController.js create mode 100644 src/controllers/TimerController.js create mode 100644 src/controllers/index.js diff --git a/src/App.js b/src/App.js index 571486d..c168ec7 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,5 @@ // src/App.js -import { useEffect, useState, useMemo, useCallback, useRef } from "react"; -import { nanoid } from "nanoid"; +import { useEffect, useCallback } from "react"; import "./App.css"; import SingleCard from "./components/singlecard/SingleCard"; import Celebration from "./components/celebration/Celebration"; @@ -9,190 +8,91 @@ import ShowConfetti from "./components/confetti/Confetti"; import GameOver from "./components/gameover/GameOver"; import CustomCursor from "./components/CustomCursor/CustomCursor"; -import { cardImages } from "./data/cardImages"; -import { numbers } from "./constants/numbers"; -import { secureShuffleArray, pickRandomImages } from "./utils/logic"; +// Import controllers +import { + useGameController, + useTimerController, + useAudioController, + useScoreController, + useHintController, +} from "./controllers"; import useTrackViewCounter from "./hooks/useTrackViewCounter"; function App() { - const [cards, setCards] = useState([]); - const [turns, setTurns] = useState(0); - const [choiceOne, setChoiceOne] = useState(null); - const [choiceTwo, setChoiceTwo] = useState(null); - const [disabled, setDisabled] = useState(false); - const [highScore, setHighScore] = useState(0); - const [matched, setMatched] = useState(0); - const [celebrationStatus, setCelebrationStatus] = useState(false); - const [elapsedTime, setElapsedTime] = useState(undefined); - const intervalRef = useRef(null); - const hintTimeoutRef = useRef(null); - const hintIntervalRef = useRef(null); - const hintLockedRef = useRef(false); - const [hintCount, setHintCount] = useState(3); - const [hintCooldown, setHintCooldown] = useState(0); - const [hintActive, setHintActive] = useState(false); - const [animateCollapse, setAnimateCollapse] = useState(false); - const [gameOverMessage, setGameOverMessage] = useState(false); - const viewCounter = useTrackViewCounter(); - const REVEAL_DURATION = 2000; - const HINT_COOLDOWN = 5000; - - const soundEffect = useMemo(() => { - const audio = new Audio(); - audio.autoplay = true; - return audio; - }, []); - - const playSound = useCallback( - (src) => { - soundEffect.src = src; - soundEffect.load(); - soundEffect.play().catch(() => {}); - }, - [soundEffect] - ); - - const resetTurn = useCallback(() => { - setChoiceOne(null); - setChoiceTwo(null); - setTurns((prev) => prev + 1); - setDisabled(false); - }, []); - - const handleTime = useCallback((start) => { - if (start) { - if (!intervalRef.current) { - intervalRef.current = setInterval(() => { - setElapsedTime((prev) => (prev || 0) + 1); - }, 1000); - } - } else if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; - } - }, []); - - const clearTimer = useCallback(() => { - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; - } - }, []); - - const hintCards = useCallback(() => { - if (hintActive || hintLockedRef.current) return; - - if (!cards || cards.length === 0) return; - if (hintCount === 0) return; - - setHintCount((c) => c - 1); - hintLockedRef.current = true; - if (elapsedTime === undefined) handleTime(true); - const seconds = Math.floor(HINT_COOLDOWN / 1000); - setHintCooldown(seconds); - if (hintIntervalRef.current) clearInterval(hintIntervalRef.current); - hintIntervalRef.current = setInterval(() => { - setHintCooldown((s) => { - if (s <= 1) { - clearInterval(hintIntervalRef.current); - hintIntervalRef.current = null; - hintLockedRef.current = false; - return 0; - } - return s - 1; - }); - }, 1000); - - const available = cards.map((c, i) => ({ c, i })).filter(({ c }) => !c.matched); - if (available.length === 0) { - if (hintIntervalRef.current) { - clearInterval(hintIntervalRef.current); - hintIntervalRef.current = null; - } - setHintCooldown(0); - hintLockedRef.current = false; - return; - } - - if (available.length === 1) { - setHintActive(true); - setDisabled(true); - setChoiceOne(available[0].c); - setChoiceTwo(null); - - if (hintTimeoutRef.current) clearTimeout(hintTimeoutRef.current); - hintTimeoutRef.current = setTimeout(() => { - setChoiceOne(null); - setChoiceTwo(null); - setHintActive(false); - setDisabled(false); - }, REVEAL_DURATION); - - return; - } - - const idxA = crypto.getRandomValues(new Uint32Array(1))[0] % available.length; - - let idxB = crypto.getRandomValues(new Uint32Array(1))[0] % (available.length - 1); - if (idxB >= idxA) idxB += 1; - - const cardA = available[idxA].c; - const cardB = available[idxB].c; - - setHintActive(true); - setDisabled(true); - setChoiceOne(cardA); - setChoiceTwo(cardB); - setTurns(turns + 1); - - if (hintTimeoutRef.current) clearTimeout(hintTimeoutRef.current); - hintTimeoutRef.current = setTimeout(() => { - setChoiceOne(null); - setChoiceTwo(null); - setHintActive(false); - setDisabled(false); - }, REVEAL_DURATION); - }, [cards, hintCount, hintActive]); - - const shuffledCards = useCallback(() => { - const selected = pickRandomImages(cardImages, 6); - const dup = [...selected, ...selected] - .sort(() => nanoid(16).localeCompare(nanoid(16))) - .map((card) => { - const crypto = globalThis.crypto || globalThis.msCrypto; - const rand = new Uint32Array(1); - crypto.getRandomValues(rand); - return { ...card, id: rand[0], matched: false }; - }); - - secureShuffleArray(numbers); - setChoiceOne(null); - setChoiceTwo(null); - setCards(dup); - setTurns(0); - setMatched(0); - setCelebrationStatus(false); - setElapsedTime(undefined); - clearTimer(); - setAnimateCollapse(true); - setTimeout(() => setAnimateCollapse(false), 1200); - setGameOverMessage(false); - }, [clearTimer]); + // Initialize controllers + const gameController = useGameController(); + const timerController = useTimerController(); + const audioController = useAudioController(); + const scoreController = useScoreController(); + const hintController = useHintController(); + const viewCounter = useTrackViewCounter(); + + // Destructure controller methods and state + const { + cards, + turns, + choiceOne, + choiceTwo, + disabled, + matched, + animateCollapse, + gameStarted, + setDisabled, + resetTurn, + initializeGame, + resetGameState: resetGame, + handleChoice: gameHandleChoice, + updateMatchedCards, + } = gameController; + + const { elapsedTime, handleTime, clearTimer, resetTimer } = timerController; + const { playSounds } = audioController; + const { + highScore, + celebrationStatus, + gameOverMessage, + checkGameCompletion, + resetGameState, + } = scoreController; + const { + hintCount, + hintCooldown, + hintActive, + hintCards, + resetHints, + cleanupHints, + } = hintController; + + // Enhanced hint handler that uses the hint controller + const handleHintClick = useCallback(() => { + hintCards( + cards, + turns, + elapsedTime, + gameController.setChoiceOne, + gameController.setChoiceTwo, + setDisabled, + gameController.setTurns, + handleTime + ); + }, [cards, turns, elapsedTime, hintCards, setDisabled, handleTime, gameController]); const handleNewGame = useCallback(() => { - setHintCount(3) - setHintCooldown(0); - playSound("audio/start.mp3"); - shuffledCards(); - }, [playSound, shuffledCards]); + resetHints(); + resetGameState(); + resetTimer(); + playSounds.start(); + initializeGame(); + }, [resetHints, resetGameState, resetTimer, playSounds, initializeGame]); const handleChoice = useCallback( (card) => { - if (disabled) return; - choiceOne ? setChoiceTwo(card) : setChoiceOne(card); - if (elapsedTime === undefined) handleTime(true); + if (elapsedTime === undefined && !gameStarted) { + handleTime(true); + } + gameHandleChoice(card); }, - [choiceOne, disabled, elapsedTime, handleTime] + [elapsedTime, gameStarted, handleTime, gameHandleChoice] ); useEffect(() => { @@ -201,65 +101,33 @@ function App() { if (choiceOne && choiceTwo) { setDisabled(true); if (choiceOne.src === choiceTwo.src) { - playSound("/audio/match.wav"); - setCards((prev) => - prev.map((c) => - c.src === choiceOne.src ? { ...c, matched: true } : c - ) - ); - setMatched((prev) => prev + 2); + playSounds.match(); + updateMatchedCards(choiceOne.src); resetTurn(); } else { - playSound("/audio/fail.wav"); + playSounds.fail(); const t = setTimeout(() => resetTurn(), 1000); return () => clearTimeout(t); } } else if (choiceOne) { - playSound("/audio/swap.wav"); + playSounds.swap(); } - }, [choiceOne, choiceTwo, resetTurn, playSound]); + }, [choiceOne, choiceTwo, hintActive, setDisabled, playSounds, updateMatchedCards, resetTurn]); useEffect(() => { if (matched === cards.length && turns) { handleTime(false); - const storedHigh = globalThis.localStorage.getItem("highscore"); - const storedRun = globalThis.localStorage.getItem("runtime"); - const better = - storedHigh === null || - turns < Number(storedHigh) || - (turns === Number(storedHigh) && elapsedTime < Number(storedRun)); - - if (better) { - globalThis.localStorage.setItem("highscore", turns); - globalThis.localStorage.setItem("runtime", elapsedTime); - playSound("audio/celebration.mp3"); - setCelebrationStatus(true); - setHighScore(turns); - setGameOverMessage(false); - } else { - setGameOverMessage(true); - } + checkGameCompletion(matched, cards.length, turns, elapsedTime, playSounds.celebration); } - }, [matched, cards.length, turns, elapsedTime, handleTime, playSound]); + }, [matched, cards.length, turns, elapsedTime, handleTime, checkGameCompletion, playSounds.celebration]); useEffect(() => { - shuffledCards(); - const hs = Number(globalThis.localStorage.getItem("highscore") || 0); - setHighScore(hs); + initializeGame(); return () => { clearTimer(); - if (hintTimeoutRef.current) { - clearTimeout(hintTimeoutRef.current); - hintTimeoutRef.current = null; - } - if (hintIntervalRef.current) { - clearInterval(hintIntervalRef.current); - hintIntervalRef.current = null; - } - hintLockedRef.current = false; - setHintCooldown(0); + cleanupHints(); }; - }, [shuffledCards, clearTimer]); + }, [initializeGame, clearTimer, cleanupHints]); return (
@@ -279,7 +147,7 @@ function App() {
-
- -

{hintCount === 1 ? "Hint Remaining: " : "Hints Remaining: "}{hintCount}

-
+
+ +
+ +

+ {hintCount === 1 ? 'Hint Remaining: ' : 'Hints Remaining: '} + {hintCount} +

+
- {cards.map((card) => ( + {cards.map(card => (

Turns: {turns}

-
+

HighScore: {highScore}

-

Runtime: {globalThis.localStorage.getItem("runtime") || 0}

+

Runtime: {globalThis.localStorage.getItem('runtime') || 0}

-

Time Elapsed: {elapsedTime || "Not started"}

+

Time Elapsed: {elapsedTime || 'Not started'}

{viewCounter !== null &&

This memory got {viewCounter} views

} {gameOverMessage && ( { const soundEffect = useMemo(() => { @@ -9,7 +9,7 @@ export const useAudioController = () => { }, []); const playSound = useCallback( - (src) => { + src => { soundEffect.src = src; soundEffect.load(); soundEffect.play().catch(() => {}); @@ -18,11 +18,11 @@ export const useAudioController = () => { ); const playSounds = { - match: () => playSound("/audio/match.wav"), - fail: () => playSound("/audio/fail.wav"), - swap: () => playSound("/audio/swap.wav"), - celebration: () => playSound("audio/celebration.mp3"), - start: () => playSound("audio/start.mp3"), + match: () => playSound('/audio/match.wav'), + fail: () => playSound('/audio/fail.wav'), + swap: () => playSound('/audio/swap.wav'), + celebration: () => playSound('audio/celebration.mp3'), + start: () => playSound('audio/start.mp3'), }; return { diff --git a/src/controllers/ErrorController.js b/src/controllers/ErrorController.js index 63cb199..041d212 100644 --- a/src/controllers/ErrorController.js +++ b/src/controllers/ErrorController.js @@ -1,5 +1,5 @@ // src/controllers/ErrorController.js -import { useState, useCallback } from "react"; +import { useState, useCallback } from 'react'; export const useErrorController = () => { const [error, setError] = useState(null); diff --git a/src/controllers/GameController.js b/src/controllers/GameController.js index 6495373..96af776 100644 --- a/src/controllers/GameController.js +++ b/src/controllers/GameController.js @@ -1,9 +1,9 @@ // src/controllers/GameController.js -import { useState, useCallback } from "react"; -import { nanoid } from "nanoid"; -import { cardImages } from "../data/cardImages"; -import { numbers } from "../constants/numbers"; -import { secureShuffleArray, pickRandomImages } from "../utils/logic"; +import { useState, useCallback } from 'react'; +import { nanoid } from 'nanoid'; +import { cardImages } from '../data/cardImages'; +import { numbers } from '../constants/numbers'; +import { secureShuffleArray, pickRandomImages } from '../utils/logic'; export const useGameController = () => { const [cards, setCards] = useState([]); @@ -18,7 +18,7 @@ export const useGameController = () => { const resetTurn = useCallback(() => { setChoiceOne(null); setChoiceTwo(null); - setTurns((prev) => prev + 1); + setTurns(prev => prev + 1); setDisabled(false); }, []); @@ -26,7 +26,7 @@ export const useGameController = () => { const selected = pickRandomImages(cardImages, 6); const dup = [...selected, ...selected] .sort(() => nanoid(16).localeCompare(nanoid(16))) - .map((card) => { + .map(card => { const crypto = globalThis.crypto || globalThis.msCrypto; const rand = new Uint32Array(1); crypto.getRandomValues(rand); @@ -50,13 +50,13 @@ export const useGameController = () => { }, []); const handleChoice = useCallback( - (card) => { + card => { if (disabled) return; - + if (!gameStarted) { setGameStarted(true); } - + if (choiceOne) { setChoiceTwo(card); } else { @@ -66,13 +66,11 @@ export const useGameController = () => { [choiceOne, disabled, gameStarted] ); - const updateMatchedCards = useCallback((src) => { - setCards((prev) => - prev.map((c) => - c.src === src ? { ...c, matched: true } : c - ) + const updateMatchedCards = useCallback(src => { + setCards(prev => + prev.map(c => (c.src === src ? { ...c, matched: true } : c)) ); - setMatched((prev) => prev + 2); + setMatched(prev => prev + 2); }, []); return { diff --git a/src/controllers/HintController.js b/src/controllers/HintController.js index b6da6ce..3315830 100644 --- a/src/controllers/HintController.js +++ b/src/controllers/HintController.js @@ -1,60 +1,99 @@ // src/controllers/HintController.js -import { useState, useCallback, useRef } from "react"; +import { useState, useCallback, useRef } from 'react'; export const useHintController = () => { const [hintCount, setHintCount] = useState(3); const [hintCooldown, setHintCooldown] = useState(0); const [hintActive, setHintActive] = useState(false); - + const hintTimeoutRef = useRef(null); const hintIntervalRef = useRef(null); const hintLockedRef = useRef(false); - + const REVEAL_DURATION = 2000; const HINT_COOLDOWN = 5000; - const hintCards = useCallback((cards, turns, elapsedTime, setChoiceOne, setChoiceTwo, setDisabled, setTurns, handleTime) => { - if (hintActive || hintLockedRef.current) return; - - if (!cards || cards.length === 0) return; - if (hintCount === 0) return; - - setHintCount((c) => c - 1); - hintLockedRef.current = true; - if (elapsedTime === undefined) handleTime(true); - - const seconds = Math.floor(HINT_COOLDOWN / 1000); - setHintCooldown(seconds); - if (hintIntervalRef.current) clearInterval(hintIntervalRef.current); - - hintIntervalRef.current = setInterval(() => { - setHintCooldown((s) => { - if (s <= 1) { + const hintCards = useCallback( + ( + cards, + turns, + elapsedTime, + setChoiceOne, + setChoiceTwo, + setDisabled, + setTurns, + handleTime + ) => { + if (hintActive || hintLockedRef.current) return; + + if (!cards || cards.length === 0) return; + if (hintCount === 0) return; + + setHintCount(c => c - 1); + hintLockedRef.current = true; + if (elapsedTime === undefined) handleTime(true); + + const seconds = Math.floor(HINT_COOLDOWN / 1000); + setHintCooldown(seconds); + if (hintIntervalRef.current) clearInterval(hintIntervalRef.current); + + hintIntervalRef.current = setInterval(() => { + setHintCooldown(s => { + if (s <= 1) { + clearInterval(hintIntervalRef.current); + hintIntervalRef.current = null; + hintLockedRef.current = false; + return 0; + } + return s - 1; + }); + }, 1000); + + const available = cards + .map((c, i) => ({ c, i })) + .filter(({ c }) => !c.matched); + if (available.length === 0) { + if (hintIntervalRef.current) { clearInterval(hintIntervalRef.current); hintIntervalRef.current = null; - hintLockedRef.current = false; - return 0; } - return s - 1; - }); - }, 1000); - - const available = cards.map((c, i) => ({ c, i })).filter(({ c }) => !c.matched); - if (available.length === 0) { - if (hintIntervalRef.current) { - clearInterval(hintIntervalRef.current); - hintIntervalRef.current = null; + setHintCooldown(0); + hintLockedRef.current = false; + return; } - setHintCooldown(0); - hintLockedRef.current = false; - return; - } - if (available.length === 1) { + if (available.length === 1) { + setHintActive(true); + setDisabled(true); + setChoiceOne(available[0].c); + setChoiceTwo(null); + + if (hintTimeoutRef.current) clearTimeout(hintTimeoutRef.current); + hintTimeoutRef.current = setTimeout(() => { + setChoiceOne(null); + setChoiceTwo(null); + setHintActive(false); + setDisabled(false); + }, REVEAL_DURATION); + + return; + } + + const idxA = + crypto.getRandomValues(new Uint32Array(1))[0] % available.length; + + let idxB = + crypto.getRandomValues(new Uint32Array(1))[0] % (available.length - 1); + if (idxB >= idxA) idxB += 1; + + const cardA = available[idxA].c; + const cardB = available[idxB].c; + setHintActive(true); setDisabled(true); - setChoiceOne(available[0].c); - setChoiceTwo(null); + setChoiceOne(cardA); + setChoiceTwo(cardB); + setTurns(turns + 1); if (hintTimeoutRef.current) clearTimeout(hintTimeoutRef.current); hintTimeoutRef.current = setTimeout(() => { @@ -63,32 +102,9 @@ export const useHintController = () => { setHintActive(false); setDisabled(false); }, REVEAL_DURATION); - - return; - } - - const idxA = crypto.getRandomValues(new Uint32Array(1))[0] % available.length; - - let idxB = crypto.getRandomValues(new Uint32Array(1))[0] % (available.length - 1); - if (idxB >= idxA) idxB += 1; - - const cardA = available[idxA].c; - const cardB = available[idxB].c; - - setHintActive(true); - setDisabled(true); - setChoiceOne(cardA); - setChoiceTwo(cardB); - setTurns(turns + 1); - - if (hintTimeoutRef.current) clearTimeout(hintTimeoutRef.current); - hintTimeoutRef.current = setTimeout(() => { - setChoiceOne(null); - setChoiceTwo(null); - setHintActive(false); - setDisabled(false); - }, REVEAL_DURATION); - }, [hintActive, hintCount]); + }, + [hintActive, hintCount] + ); const resetHints = useCallback(() => { setHintCount(3); diff --git a/src/controllers/ScoreController.js b/src/controllers/ScoreController.js index 49d05e2..7b2c89d 100644 --- a/src/controllers/ScoreController.js +++ b/src/controllers/ScoreController.js @@ -1,5 +1,5 @@ // src/controllers/ScoreController.js -import { useState, useCallback, useEffect } from "react"; +import { useState, useCallback, useEffect } from 'react'; export const useScoreController = () => { const [highScore, setHighScore] = useState(0); @@ -7,31 +7,34 @@ export const useScoreController = () => { const [gameOverMessage, setGameOverMessage] = useState(false); const loadHighScore = useCallback(() => { - const hs = Number(globalThis.localStorage.getItem("highscore") || 0); + const hs = Number(globalThis.localStorage.getItem('highscore') || 0); setHighScore(hs); }, []); - const checkGameCompletion = useCallback((matched, cardsLength, turns, elapsedTime, onCelebration) => { - if (matched === cardsLength && turns) { - const storedHigh = globalThis.localStorage.getItem("highscore"); - const storedRun = globalThis.localStorage.getItem("runtime"); - const better = - storedHigh === null || - turns < Number(storedHigh) || - (turns === Number(storedHigh) && elapsedTime < Number(storedRun)); + const checkGameCompletion = useCallback( + (matched, cardsLength, turns, elapsedTime, onCelebration) => { + if (matched === cardsLength && turns) { + const storedHigh = globalThis.localStorage.getItem('highscore'); + const storedRun = globalThis.localStorage.getItem('runtime'); + const better = + storedHigh === null || + turns < Number(storedHigh) || + (turns === Number(storedHigh) && elapsedTime < Number(storedRun)); - if (better) { - globalThis.localStorage.setItem("highscore", turns); - globalThis.localStorage.setItem("runtime", elapsedTime); - setCelebrationStatus(true); - setHighScore(turns); - setGameOverMessage(false); - if (onCelebration) onCelebration(); - } else { - setGameOverMessage(true); + if (better) { + globalThis.localStorage.setItem('highscore', turns); + globalThis.localStorage.setItem('runtime', elapsedTime); + setCelebrationStatus(true); + setHighScore(turns); + setGameOverMessage(false); + if (onCelebration) onCelebration(); + } else { + setGameOverMessage(true); + } } - } - }, []); + }, + [] + ); const resetGameState = useCallback(() => { setCelebrationStatus(false); diff --git a/src/controllers/TimerController.js b/src/controllers/TimerController.js index 0532bff..c42bc51 100644 --- a/src/controllers/TimerController.js +++ b/src/controllers/TimerController.js @@ -1,15 +1,15 @@ // src/controllers/TimerController.js -import { useState, useCallback, useRef } from "react"; +import { useState, useCallback, useRef } from 'react'; export const useTimerController = () => { const [elapsedTime, setElapsedTime] = useState(undefined); const intervalRef = useRef(null); - const handleTime = useCallback((start) => { + const handleTime = useCallback(start => { if (start) { if (!intervalRef.current) { intervalRef.current = setInterval(() => { - setElapsedTime((prev) => (prev || 0) + 1); + setElapsedTime(prev => (prev || 0) + 1); }, 1000); } } else if (intervalRef.current) {