This is a simple countdown game built with React  . The primary focus of this project is to demonstrate the use of 
useRef.
The game counts down from a specified time and updates the UI in real-time.
- Real-time countdown timer
- Start, and reset functionality
- Simple and clean user interface
- Focus on the useRefhook for managing timer state and modal visibility
- React
- JavaScript (ES6+)
- HTML5
- CSS3
In this project, useRef is used to manage the timer's state and control the visibility of the result modal. 
Unlike useState, which triggers a re-render when updated, useRef provides a way to persist values between renders without causing a re-render.
-  useRefis used to createtimeranddialogreferences for managing the interval timer and modal visibility.
-  useImperativeHandlein theResultModalcomponent allows the parent component to control the modal's visibility by exposing theopenmethod.
-  The TimerChallengecomponent manages the countdown logic and interacts with theResultModalcomponent through refs.
Between TimerChallenge and ResultModal:
- Parent-Child Relationship:
- TimerChallenge: The parent component that holds the timer logic and manages the state of the challenge.
- ResultModal: The child component that displays the result modal.- TimerChallengecontrols it using a ref.
 
import { useState, useRef } from 'react';
import ResultModal from './ResultModal.jsx';
export default function TimerChallenge({ title, targetTime }) {
  const dialog = useRef(); 
  
    if (timeRemaining <= 0) {
    clearInterval(timer.current); 
    dialog.current.open(); // Calls the open method 
  }
  ...
}import { forwardRef, useImperativeHandle, useRef } from 'react';
import { createPortal } from 'react-dom';
const ResultModal = forwardRef(function ResultModal(
  { targetTime, remainingTime, onReset },  ref) {
      const dialog = useRef();
      ...
});
export default ResultModal;- useImperativeHandle:- This hook is used inside ResultModalto expose a customopenmethod.
- This method is used to show the modal dialog when called.
 
- This hook is used inside 
useImperativeHandle(ref, () => {
   return {
     // This method is used to show the modal dialog when called.
     open() {
       dialog.current.showModal(); // Show the modal dialog
     },
   };  
});- createPortal:- The createPortalfunction is used to render the modal in a different part of the DOM.
- This allows the modal to overlay other content and not be constrained by the parent component's styling or structure.
 
- The 
return createPortal(
    <dialog ref={dialog} className="result-modal">
    ...
    </dialog>,
    document.getElementById('modal') // Attach the modal to the DOM element with id 'modal'
  );
});
export default ResultModal;- 
In TimerChallenge:- A ref (dialog) is created usinguseRef.
- This ref is passed to ResultModalusing therefattribute.
- When the timer reaches zero, dialog.current.open()is called to show the modal.
 
- A ref (
- 
In ResultModal:- useImperativeHandlecustomizes the ref to expose the- openmethod.
- The openmethod usesdialog.current.showModal()to display the modal.
 
- Parent Component: TimerChallengeis the parent component because it renders and controlsResultModal.
- Child Component: ResultModalis the child component, which usesuseImperativeHandleto expose itsopenmethod to the parent component.
- Interaction: TimerChallengecan programmatically controlResultModalthrough the ref and the exposedopenmethod, allowing it to open the modal when needed.
import { useState, useRef } from 'react';
import ResultModal from './ResultModal.jsx';
export default function TimerChallenge({ title, targetTime }) {
  const timer = useRef();
  const dialog = useRef();
  const [timeRemaining, setTimeRemaining] = useState(targetTime * 1000);
  const timerIsActive = timeRemaining > 0 && timeRemaining < targetTime * 1000;
  if (timeRemaining <= 0) {
    clearInterval(timer.current);
    dialog.current.open();
  }
  function handleReset() {
    setTimeRemaining(targetTime * 1000);
  }
  function handleStart() {
    timer.current = setInterval(() => {
      setTimeRemaining((prevTimeRemaining) => prevTimeRemaining - 10);
    }, 10);
  }
  function handleStop() {
    dialog.current.open();
    clearInterval(timer.current);
  }
  return (
    <>
      <ResultModal
        ref={dialog}
        targetTime={targetTime}
        remainingTime={timeRemaining} 
        onReset={handleReset}
      />
      <section className="challenge">
        <h2>{title}</h2>
        <p className="challenge-time">
          {targetTime} second{targetTime > 1 ? 's' : ''}
        </p>
        <p>
          <button onClick={timerIsActive ? handleStop : handleStart}>
            {timerIsActive ? 'Stop' : 'Start'} Challenge
          </button>
        </p>
        <p className={timerIsActive ? 'active' : undefined}>
          {timerIsActive ? 'Time is running...' : 'Timer inactive'}
        </p>
      </section>
    </>
  );
}import { forwardRef, useImperativeHandle, useRef } from 'react';
import { createPortal } from 'react-dom';
const ResultModal = forwardRef(function ResultModal(
  { targetTime, remainingTime, onReset },
  ref
) {
  const dialog = useRef();
  const userLost = remainingTime <= 0;
  const formattedRemainingTime = (remainingTime / 1000).toFixed(2);
  const score = Math.round((1 - remainingTime / (targetTime * 1000)) * 100);
  useImperativeHandle(ref, () => ({
    open() {
      dialog.current.showModal();
    },
  }));
  return createPortal(
    <dialog ref={dialog} className="result-modal">
      {userLost && <h2>You lost</h2>}
      {!userLost && <h2>Your Score: {score}</h2>}
      <p>
        The target time was <strong>{targetTime} seconds.</strong>
      </p>
      <p>
        You stopped the timer with <strong>{formattedRemainingTime} seconds left.</strong>
      </p>
      <form method="dialog" onSubmit={onReset}>
        <button>Close</button>
      </form>
    </dialog>,
    document.getElementById('modal')
  );
});
export default ResultModal;import { useState, useRef } from 'react';
export default function Player() {
  const playerName = useRef();
  const [enteredPlayerName, setEnteredPlayerName] = useState(null);
  function handleClick() {
    setEnteredPlayerName(playerName.current.value);
    playerName.current.value = '';
  }
  return (
    <section id="player">
      <h2>Welcome {enteredPlayerName ?? 'unknown entity'}</h2>
      <p>
        <input ref={playerName} type="text" />
        <button onClick={handleClick}>Set Name</button>
      </p>
    </section>
  );
}import Player from './components/Player.jsx';
import TimerChallenge from './components/TimerChallenge.jsx';
function App() {
  return (
    <>
      {/* Render the Player component */}
      <Player />
      <div id="challenges">
        {/* Render multiple TimerChallenge components with different 'target times' */}
        <TimerChallenge title="Easy" targetTime={1} />
        <TimerChallenge title="Not easy" targetTime={5} />
        <TimerChallenge title="Getting tough" targetTime={10} />
        <TimerChallenge title="Pros only" targetTime={15} />
      </div>
    </>
  );
}
export default App;
- Clone the repository:
git clone https://github.com/yourusername/react-countdown-game.git 
- Navigate to the project directory:
cd react-countdown-game
- Install the dependencies:
npm install 
- Start the development server:
npm start 
- Open your browser and go to http://localhost:5173.
Feel free to dive into the code to understand the implementation details. Happy coding! πππ©βπ»
Copyright Β© Shani Bider, 2024

