Building a Stop Watch App with Next.js
Day 25: Stop Watch — 30 Days of 30 Projects Challenge
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:
- Start the Development Server: Run
npm run dev
to start the development server. - 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:
- Twitter: @0xAsharib
- LinkedIn: Asharib Ali
- GitHub: AsharibAli
- Website: asharib.xyz
Thanks for reading!