author-pic

Michael Jude Larocca

How to Make a Best Time React Component


Published on July 03, 2023

In this article, we build a React best time game component. You will learn how to work with the date-fns and UUID libraries, React hooks, and local storage, gaining skills for your original projects!


TXG-93


Introduction

What does the best time React component do? This component is used in games to capture a player's best time. The best time is the least amount of time it takes the player to complete the game. The player's best time is stored in local storage.

I created this best-time React component with the intention of using it in several upcoming projects ( I mean, that is what React components are all about, right?) Although I've been coding now for quite some time, I haven't yet created a React component that was reused in other apps, nor have I learned how to create one in any of the tutorials I'm taking.

After I created the component, I realized a logical flaw on my part. Since I'm utilizing local storage to store the storedBestTime variable, when this component is used in other projects, the same storedBestTime variable will be updated by each project, which defeats the whole purpose of creating a reusable component.

I solved this issue by utilizing the UUID library I recently learned about to generate a unique game ID for the storedBestTime variable, and I refactored to code to utilize it.


Setting up the environment

First, we need to set up our coding environment.

The React environment

For this project, I used the VS Code editor to create a React app with Vite. Afterward, I converted it into a GitHub repository and deployed it on Netlify.

If you would like to learn how to set up a local React development environment, I wrote the following two beginner-friendly articles:

Install the date-fns library

To install the date-fns library using Node Package Manager (npm), open your terminal and run the following command:

npm install date-fns

The date-fns library is a lightweight JavaScript tool for managing dates and times. It offers functions for parsing, formatting, manipulating, and comparing dates, allowing tasks like date string formatting and time unit calculations. Its modular design ensures efficient and easy-to-maintain code.

Install the UUID library

The following command npm install uuid installs the UUID library as a dependency in your Node.js project using the Node Package Manager (npm).

npm install uuid

Building the best time react component

At the top of the App JSX file, we will import the necessary hooks, libraries, and CSS files:

  • useState
  • useEffect
  • date-fns
  • uuid
  • App.css
import React, { useState, useEffect } from "react";
import { format } from "date-fns";
import { v4 as uuidv4 } from "uuid";

App function

We will be writing our code inside this App function:

export default function BestTime() {
  
  });

Inside the App function, we initially set up the variables.

Variables initialized:

  • gameID: A unique identifier generated using the UUID library for each instance of the game, ensuring that the best time for each game is stored separately in the browser's local storage.
  • currentTime: Stores the current time of the game
  • gameEndTime: Stores the time when the game ends
  • gameBestTime: Stores the best time achieved in the game
  • gameStarted: A boolean indicating if the game has started
  • gameEnded: A boolean indicating if the game has ended
    const [gameID, setGameID] = useState(getOrSetGameID());    
    const [currentTime , setCurrentTime ] = useState(0);
	const [gameEndTime, setGameEndTime] = useState(0);
	const [gameBestTime, setGameBestTime] = useState(getBestTime());
	const [gameStarted, setGameStarted] = useState(false);
	const [gameEnded, setGameEnded] = useState(false);

The currentTime and gameEndTime variables are initially set to zero. The gameStarted and gameEnded are boolean variables and are initially set to false.

The gameBestTime variable calls the getBestTime() function to set the initial value.


Game ID function

The getOrSetGameID function checks if there's an existing game ID in the browser's local storage. If it finds one, it returns that game ID. Otherwise, it creates a new unique game ID using the UUID library and stores it in the local storage before returning the new game ID.

	function getOrSetGameID() {
		const storedGameID = localStorage.getItem("gameID");
		if (storedGameID) {
		  return storedGameID;
		} else {
		  const newGameID = uuidv4();
		  localStorage.setItem("gameID", newGameID);
		  return newGameID;
		}
	  }

Best time function

The getBestTime function retrieves the best time achieved in the game from the browser's local storage. If there is a stored best time, it converts the stored value to an integer and returns it. If there is no stored best time, it returns Infinity, indicating that no best time has been achieved yet.

As Infinity is a value that is greater than any other number. This makes it easier to compare and update the best time when a new value is recorded.

  function getBestTime() {
    const storedBestTime = localStorage.getItem(`bestTime-${gameID}`);
    return storedBestTime ? parseInt(storedBestTime, 10) : Infinity;
  }

Save best time function

The saveBestTime function takes a single argument, bestime, which represents the best time achieved in the game. The function uses the localStorage.setItem method to save this value in the browser's local storage. The key for this value is "bestTime", and the value being stored is bestime. This way, the best time can be retrieved and displayed even after the user closes and reopens the browser.

  function saveBestTime(bestime) {
    localStorage.setItem(`bestTime-${gameID}`, bestime);
  }

The best time function

The bestTime function checks if the current game's end time (gameEndTime) is less than the previously stored best time (gameBestTime) and if the game has ended (indicated by gameEndTime not being 0). If both conditions are met, it updates the best time by calling setGameBestTime with the new best time (gameEndTime) and saves this new best time in the browser's local storage using the saveBestTime function.

	function bestTime() {
		if (gameEndTime < gameBestTime && gameEndTime !== 0) {
			setGameBestTime(gameEndTime);
			saveBestTime(gameEndTime);
		}
	}

The reset time function

The resetTime function sets the current time (currentTime) and the game end time (gameEndTime) back to 0, essentially resetting the timer for a new game.

	function resetTime() {
		setCurrentTime(0);
		setGameEndTime(0);
	}

The start game function

The startGame function sets the gameStarted variable to true and the gameEnded variable to false, indicating that a new game has begun.

	function startGame() {
		setGameStarted(true);
		setGameEnded(false);
	}

The end game function

The endGame function sets the gameEnded variable to true, the gameStarted variable to false, and updates the gameEndTime variable with the value of currentTime. This indicates that the game has ended and records the time at which the game finished.

	function endGame() {
		setGameEnded(true);
		setGameStarted(false);
		setGameEndTime(currentTime);
	}

The formatted time function

The formattedTime function takes a time value as input and checks if it's Infinity or not a number (NaN). If it is, the function returns a placeholder string "--:--:--". If it's a valid number, the function creates a new Date object with the time value multiplied by 10, formats the date into a "mm:ss:SS" format, and returns the formatted string.

	function formattedTime(timeValue) {
		if (timeValue === Infinity || isNaN(timeValue)) {
		  return "--:--:--";
		}
		const date = new Date(timeValue * 10);
		return format(date, "mm:ss:SS");
	  }

The React useEffect Hooks

For the best time react component, we create useEffect hooks to trigger when the gameStarted, gameEndTime, and gameEnded variables change.

When the gameStarted variable changes, this useEffect hook is triggered. If gameStarted is true, it calls the resetTime() function to reset the timer for a new game. The hook ensures that the timer is reset every time a new game begins.

	useEffect(() => {
		if (gameStarted) {
			resetTime();
		}
	}, [gameStarted]);

This useEffect hook is triggered when the gameEndTime variable changes. It calls the bestTime() function to check and update the best time achieved in the game.

	useEffect(() => {
		bestTime();
	}, [gameEndTime]);

The last useEffect hook is triggered when either the gameStarted or gameEnded variables change. If the game has started and not ended, it sets up an interval to update the currentTime variable every 10 milliseconds. When the component unmounts or the game ends, the interval is cleared to prevent memory leaks.

To prevent the component from malfunctioning when the timer exceeds an hour, the if statement clears the interval, stops the timer, and returns the current time.

	useEffect(() => {
		if (gameStarted && !gameEnded) {
		  const intervalId = setInterval(() => {
			setCurrentTime((prevTime) => {
			  if (prevTime >= 360000) {
				clearInterval(intervalId);
				return prevTime;
			  }
			  return prevTime + 1;
			});
		  }, 10);
		  // Clean up the interval on component unmount
		  return () => {
			clearInterval(intervalId);
		  };
		}
	  }, [gameStarted, gameEnded]);

Render the Component

The component renders a section containing the current time and best time, both formatted using the formattedTime function. Below the section, two buttons are displayed: "Start Game" and "End Game". The "Start Game" button is disabled when the game is already in progress, and clicking on the "End Game" button ends the current game.

	return (
		<>
			<section className="best-time">
				<div className="inner-border">
					<div>Time: {formattedTime(currentTime)}</div>
				</div>
				<div className="inner-border">
					<div>Best Time: {formattedTime(gameBestTime)}</div>
				</div>
			</section>
			<button onClick={startGame} disabled={gameStarted}>
				Start Game
			</button>
			<button onClick={endGame}>End Game</button>
		</>
	);

When incorporating this component into a React project, the "Start Game" and "End Game" buttons are meant to be removed after the code is revised accordingly. When the code is revised, return only the HTML section element.

    return (	
            <section className="best-time">
				<div className="inner-border">
					<div>Time: {formattedTime(currentTime)}</div>
				</div>
				<div className="inner-border">
					<div>Best Time: {formattedTime(gameBestTime)}</div>
				</div>
			</section>
       );

Here is the complete best time component

import React, { useState, useEffect } from "react";
import { format } from "date-fns";
import { v4 as uuidv4 } from "uuid";

export default function BestTime() {
	const [gameID, setGameID] = useState(getOrSetGameID());
	const [currentTime , setCurrentTime ] = useState(0);
	const [gameEndTime, setGameEndTime] = useState(0);
	const [gameBestTime, setGameBestTime] = useState(getBestTime());
	const [gameStarted, setGameStarted] = useState(false);
	const [gameEnded, setGameEnded] = useState(false);

	function getOrSetGameID() {
		const storedGameID = localStorage.getItem("gameID");
		if (storedGameID) {
		  return storedGameID;
		} else {
		  const newGameID = uuidv4();
		  localStorage.setItem("gameID", newGameID);
		  return newGameID;
		}
	  }

	function getBestTime() {
		const storedBestTime = localStorage.getItem(`bestTime-${gameID}`);
		return storedBestTime ? parseInt(storedBestTime, 10) : Infinity;
	  }

	function saveBestTime(bestime) {
		localStorage.setItem(`bestTime-${gameID}`, bestime);
	  }

	function bestTime() {
		if (gameEndTime < gameBestTime && gameEndTime !== 0) {
			setGameBestTime(gameEndTime);
			saveBestTime(gameEndTime);
		}
	}

	function resetTime() {
		setCurrentTime(0);
		setGameEndTime(0);
	}

	function startGame() {
		setGameStarted(true);
		setGameEnded(false);
	}

	function endGame() {
		setGameEnded(true);
		setGameStarted(false);
		setGameEndTime(currentTime);
	}

	function formattedTime(timeValue) {
		if (timeValue === Infinity || isNaN(timeValue)) {
		  return "--:--:--";
		}
		const date = new Date(timeValue * 10);
		return format(date, "mm:ss:SS");
	  }

	useEffect(() => {
		if (gameStarted) {
			resetTime();
		}
	}, [gameStarted]);

	useEffect(() => {
		bestTime();
	}, [gameEndTime]);

	useEffect(() => {
		if (gameStarted && !gameEnded) {
		  const intervalId = setInterval(() => {
			setCurrentTime((prevTime) => {
			  if (prevTime >= 360000) {
				clearInterval(intervalId);
				return prevTime;
			  }
			  return prevTime + 1;
			});
		  }, 10);
		  // Clean up the interval on component unmount
		  return () => {
			clearInterval(intervalId);
		  };
		}
	  }, [gameStarted, gameEnded]);

	return (
		<>
			<section className="best-time">
				<div className="inner-border">
					<div>Time: {formattedTime(currentTime)}</div>
				</div>
				<div className="inner-border">
					<div>Best Time: {formattedTime(gameBestTime)}</div>
				</div>
			</section>
			<button onClick={startGame} disabled={gameStarted}>
				Start Game
			</button>
			<button onClick={endGame}>End Game</button>
		</>
	);
}

Note: I did have an issue with the initial best time not saving on two of my computers that was resolved by clearing the local storage.


The finished project

Here are the links to the finished project:


best time component


My other related articles


Conclusion

In this article, we created a reusable best time React component for tracking players' best times in games. We covered setting up the coding environment, building the component with functions and hooks, and rendering the component with a user interface.

To make sure this component can be used in multiple apps, we used the UUID library to generate unique game IDs for local storage. We also utilized the date-fns and UUID libraries to manage time formatting.

To take a quote from my favorite movie, "This is where the fun begins!" Now it's time to use this best time component in upcoming React projects!


Let's connect! I'm active on LinkedIn and Twitter.


Are you now adept at building a best time React component for your gaming projects? Have you discovered any other useful techniques to create reusable components? Please share the article and comment!

Please share it!