Building a Stop Watch App with Next.js

Day 25: Stop Watch — 30 Days of 30 Projects Challenge

Asharib Ali
7 min readOct 2, 2024

Hey everyone, I hope you’re all doing well and enjoying your coding journey, Am I right? By the way, today marks the 25th day of the 30-day of 30-projects challenge. Our 25th project will be creating a mini Next.js application — a stop watch. Please make sure to clap and comment on this blog post. It motivates me to create more amazing content like this, Let’s get started straight away.

Overview of the Mini Next.js Application

Our Stop Watch application allows users to:

  • Start, stop, and reset the stopwatch
  • Display elapsed time
  • Lap timer functionality

Tech-Stack Used:

  • Next.js: A React framework for building full-stack web applications.
  • React: A JavaScript library for building user interfaces.
  • Tailwind CSS: A utility-first CSS framework for styling.
  • Shadcn UI: Beautifully designed tailwindcss components that you can copy and paste into your application.
  • Vercel: For deploying the Nextjs web application.

Initialize a Nextjs project

Start by following this guide to set up the new project.

  • Go to the “components” folder.
  • Create a new file named “stop-watch.tsx”.
  • This file will manage the entire functionality of the project.

We will go through the code step-by-step to make it easy to understand.

Component Breakdown

Import Statements

"use client";
import { useState, useEffect } from "react";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
Table,
TableHeader,
TableRow,
TableHead,
TableBody,
TableCell,
} from "@/components/ui/table";
  • Enables client-side rendering.
  • Imports necessary hooks and custom components for building the UI.

Define Types

type LapTime = number;
  • Defines the LapTime type to represent lap times.

Component Definition and Initial State

export default function StopWatchComponent() {
const [isRunning, setIsRunning] = useState<boolean>(false);
const [time, setTime] = useState<number>(0);
const [lapTimes, setLapTimes] = useState<LapTime[]>([]);
  • Defines the StopWatchComponent function.
  • Manages state for whether the stopwatch is running, the elapsed time, and the list of lap times.

Handle Stopwatch Timer

useEffect(() => {
let interval: NodeJS.Timeout;
if (isRunning) {
interval = setInterval(() => {
setTime((prevTime) => prevTime + 10);
}, 10);
}
return () => clearInterval(interval);
}, [isRunning]);
  • Uses useEffect to handle the stopwatch timer.
  • Updates the elapsed time every 10 milliseconds when the stopwatch is running.

Start, Stop, Reset, and Lap Functions

const handleStart = () => {
setIsRunning(true);
};

const handleStop = () => {
setIsRunning(false);
};
const handleReset = () => {
setIsRunning(false);
setTime(0);
setLapTimes([]);
};
const handleLap = () => {
setLapTimes((prevLapTimes) => [...prevLapTimes, time]);
};
  • Functions to handle starting, stopping, resetting the stopwatch, and recording lap times.

Calculate Elapsed Tim

const minutes = Math.floor(time / 60000);
const seconds = Math.floor((time % 60000) / 1000);
const milliseconds = Math.floor((time % 1000) / 10);
  • Calculates minutes, seconds, and milliseconds from the elapsed time.

Render Stopwatch UI

return (
<div className="flex items-center justify-center min-h-screen bg-gray-100 p-4">
<Card className="w-full max-w-lg">
<CardHeader className="flex flex-col items-center justify-center">
<CardTitle className="text-5xl font-bold">Stopwatch</CardTitle>
<CardDescription className="text-lg text-gray-600">
Track your time with this stopwatch.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center justify-center gap-8 p-4">
{/* Display the elapsed time */}
<div className="text-8xl font-bold">
{minutes.toString().padStart(2, "0")}:
{seconds.toString().padStart(2, "0")}.
{milliseconds.toString().padStart(2, "0")}
</div>
{/* Buttons to control the stopwatch */}
<div className="flex gap-4">
<Button
onClick={isRunning ? handleStop : handleStart}
className="px-6 py-2 text-lg font-medium rounded-lg"
>
{isRunning ? "Stop" : "Start"}
</Button>
<Button
onClick={handleReset}
className="px-6 py-2 text-lg font-medium rounded-lg"
>
Reset
</Button>
<Button
onClick={handleLap}
className="px-6 py-2 text-lg font-medium rounded-lg"
>
Lap
</Button>
</div>
{/* Display the list of lap times */}
<div className="w-full max-w-md">
<Card className="overflow-hidden">
<CardHeader className="bg-gray-200">
<CardTitle className="text-xl font-semibold">
Lap Times
</CardTitle>
</CardHeader>
<CardContent className="max-h-[300px] overflow-auto p-0">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-left">Lap</TableHead>
<TableHead className="text-right">Time</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{lapTimes.map((lapTime, index) => (
<TableRow key={index}>
<TableCell className="font-medium">
{index + 1}
</TableCell>
<TableCell className="text-right">
{Math.floor(lapTime / 60000)
.toString()
.padStart(2, "0")}
:
{Math.floor((lapTime % 60000) / 1000)
.toString()
.padStart(2, "0")}
:
{Math.floor((lapTime % 1000) / 10)
.toString()
.padStart(2, "0")}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
</CardContent>
</Card>
</div>
);
}
  • Renders the stopwatch UI, including elapsed time, control buttons, and the list of lap times.

(Bonus just for you): Full Code with Comments

"use client"; // Enables client-side rendering for this component

import { useState, useEffect } from "react"; // Import useState and useEffect hooks from React
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "@/components/ui/card"; // Import custom Card components
import { Button } from "@/components/ui/button"; // Import custom Button component
import {
Table,
TableHeader,
TableRow,
TableHead,
TableBody,
TableCell,
} from "@/components/ui/table"; // Import custom Table components

// Define the LapTime type
type LapTime = number;

export default function StopWatch() {
// State to manage whether the stopwatch is running
const [isRunning, setIsRunning] = useState<boolean>(false);
// State to manage the elapsed time in milliseconds
const [time, setTime] = useState<number>(0);
// State to manage the list of lap times
const [lapTimes, setLapTimes] = useState<LapTime[]>([]);

// useEffect to handle the stopwatch timer
useEffect(() => {
let interval: NodeJS.Timeout;
if (isRunning) {
interval = setInterval(() => {
setTime((prevTime) => prevTime + 10);
}, 10);
}
return () => clearInterval(interval);
}, [isRunning]);

// Function to handle starting the stopwatch
const handleStart = () => {
setIsRunning(true);
};

// Function to handle stopping the stopwatch
const handleStop = () => {
setIsRunning(false);
};

// Function to handle resetting the stopwatch
const handleReset = () => {
setIsRunning(false);
setTime(0);
setLapTimes([]);
};

// Function to handle recording a lap time
const handleLap = () => {
setLapTimes((prevLapTimes) => [...prevLapTimes, time]);
};

// Calculate minutes, seconds, and milliseconds from the elapsed time
const minutes = Math.floor(time / 60000);
const seconds = Math.floor((time % 60000) / 1000);
const milliseconds = Math.floor((time % 1000) / 10);

// JSX return statement rendering the Stopwatch UI
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100 p-4">
<Card className="w-full max-w-lg">
<CardHeader className="flex flex-col items-center justify-center">
<CardTitle className="text-5xl font-bold">Stopwatch</CardTitle>
<CardDescription className="text-lg text-gray-600">
Track your time with this stopwatch.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center justify-center gap-8 p-4">
{/* Display the elapsed time */}
<div className="text-8xl font-bold">
{minutes.toString().padStart(2, "0")}:
{seconds.toString().padStart(2, "0")}.
{milliseconds.toString().padStart(2, "0")}
</div>
{/* Buttons to control the stopwatch */}
<div className="flex gap-4">
<Button
onClick={isRunning ? handleStop : handleStart}
className="px-6 py-2 text-lg font-medium rounded-lg"
>
{isRunning ? "Stop" : "Start"}
</Button>
<Button
onClick={handleReset}
className="px-6 py-2 text-lg font-medium rounded-lg"
>
Reset
</Button>
<Button
onClick={handleLap}
className="px-6 py-2 text-lg font-medium rounded-lg"
>
Lap
</Button>
</div>
{/* Display the list of lap times */}
<div className="w-full max-w-md">
<Card className="overflow-hidden">
<CardHeader className="bg-gray-200">
<CardTitle className="text-xl font-semibold">
Lap Times
</CardTitle>
</CardHeader>
<CardContent className="max-h-[300px] overflow-auto p-0">
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-left">Lap</TableHead>
<TableHead className="text-right">Time</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{lapTimes.map((lapTime, index) => (
<TableRow key={index}>
<TableCell className="font-medium">
{index + 1}
</TableCell>
<TableCell className="text-right">
{Math.floor(lapTime / 60000)
.toString()
.padStart(2, "0")}
:
{Math.floor((lapTime % 60000) / 1000)
.toString()
.padStart(2, "0")}
:
{Math.floor((lapTime % 1000) / 10)
.toString()
.padStart(2, "0")}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
</CardContent>
</Card>
</div>
);
}

Okay, you’ve completed the main component with functional UI. Now, you need to import this component into the app directory to use it in the app/page.tsx file. Your final code should look like this:

import StopWatch from "@/components/stop-watch";

export default function Home() {
return (
<div>
<StopWatch />
</div>
);
}

Running the Project

To see the stop watch in action, follow these steps:

  1. Start the Development Server: Run npm run dev to start the development server.
  2. Open in Browser: Open http://localhost:3000 in your browser to view the application.

Make sure to test it properly (each and everything) so that we don’t have any errors in the production mode aka when we host on the internet.

Now, we want people to see our application on the internet. All you have to do is create a repository on GitHub and then push your code to it. After that, deploy the Stop Watch application using Vercel.

Once you’re done with the deployment, please share the application link with me by commenting on this blog post, on Linkedin, and (most importantly, on X a.k.a. Twitter. Tag me there, and I’ll reply and appreciate your efforts) 👀

(Optional): One thing you can do on your own is to add new functionalities, enhance the styling, and improve the overall application. This way, you’ll learn something new by making modifications.

✨ Star Github Repository of the project 👈

Conclusion

In this blog post, we built a Stopwatch application using Next.js. We covered:

  • Setting up the project and using client-side rendering.
  • Managing state and timers in a React application.
  • Implementing lap timer functionality and displaying lap times.

See you tomorrow with the latest project. Happy coding!

Stay updated with the latest in cutting-edge technology! Follow me:

Thanks for reading!

--

--

Asharib Ali
Asharib Ali

Written by Asharib Ali

✨ I build & teach about AI and Blockchain stuffs⚡

Responses (2)