Building a BMI Calculator App with Next.js

Day 11: BMI Calculator — 30 Days of 30 Projects Challenge

Asharib Ali
8 min readSep 12, 2024

Hey everyone, I hope you’re all doing well and enjoying your coding journey, Am I right? By the way, today marks the eleventh day of the 30-day of 30-projects challenge. Our 11th project will be creating a mini Next.js application — a bmi calculator. 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 BMI Calculator application allows users to:

  • Input for height and weight
  • Calculate and display BMI
  • Display BMI category (underweight, normal, overweight, obese)

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 “bmi-calculator.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, ChangeEvent } from "react";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
  • Enables client-side rendering.
  • Imports necessary hooks and custom components.

State Hooks

const [height, setHeight] = useState<string>("");
const [weight, setWeight] = useState<string>("");
const [result, setResult] = useState<BmiResult | null>(null);
const [error, setError] = useState<string>("");
  • height: Stores the height input as a string.
  • weight: Stores the weight input as a string.
  • result: Stores the BMI result, which can be of type BmiResult or null if no result is calculated yet.
  • error: Stores any error message as a string.

Purpose: These state hooks manage the user inputs for height and weight, the calculated BMI result, and any error messages that may arise during input validation or calculation.

Event Handlers

const handleHeightChange = (e: ChangeEvent<HTMLInputElement>): void => {
setHeight(e.target.value);
};

const handleWeightChange = (e: ChangeEvent<HTMLInputElement>): void => {
setWeight(e.target.value);
};
  • handleHeightChange Function: Updates the height state with the value from the input field whenever it changes.
  • handleWeightChange Function: Updates the weight state with the value from the input field whenever it changes.

Purpose: These functions ensure that the height and weight state variables are kept up to date with user input.

BMI Calculation Function

const calculateBmi = (): void => {
if (!height || !weight) {
setError("Please enter both height and weight.");
return;
}

const heightInMeters = parseFloat(height) / 100;
if (heightInMeters <= 0) {
setError("Height must be a positive number.");
return;
}
const weightInKg = parseFloat(weight);
if (weightInKg <= 0) {
setError("Weight must be a positive number.");
return;
}
const bmiValue = weightInKg / (heightInMeters * heightInMeters);
let category = "";
if (bmiValue < 18.5) {
category = "Underweight";
} else if (bmiValue >= 18.5 && bmiValue < 25) {
category = "Normal";
} else if (bmiValue >= 25 && bmiValue < 30) {
category = "Overweight";
} else {
category = "Obese";
}
setResult({ bmi: bmiValue.toFixed(1), category });
setError("");
};
  • Input Validation: Checks if both height and weight are provided. If not, sets an error message.
  • Height Conversion: Converts the height from centimeters to meters and ensures it is a positive number. If not, sets an error message.
  • Weight Validation: Ensures the weight is a positive number. If not, sets an error message.
  • BMI Calculation: Calculates the BMI using the formula weight / (height * height) and determines the category based on the BMI value.
  • Result Update: Sets the result state with the calculated BMI value and its category, formatted to one decimal place. Clears any previous error messages.

JSX Return Statement

return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-900">
<Card className="w-full max-w-md mx-auto">
<CardHeader>
<CardTitle>BMI Calculator</CardTitle>
<CardDescription>
Enter your height and weight to calculate your BMI.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-2">
<Label htmlFor="height">Height (cm)</Label>
<Input
id="height"
type="number"
placeholder="Enter your height"
value={height}
onChange={handleHeightChange}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="weight">Weight (kg)</Label>
<Input
id="weight"
type="number"
placeholder="Enter your weight"
value={weight}
onChange={handleWeightChange}
/>
</div>
<Button onClick={calculateBmi}>Calculate</Button>
{error && <div className="text-red-500 text-center">{error}</div>}
{result && (
<div className="grid gap-2">
<div className="text-center text-2xl font-bold">{result.bmi}</div>
<div className="text-center text-muted-foreground">
{result.category}
</div>
</div>
)}
</CardContent>
</Card>
</div>
);
  • Container Div: Centers the BMI calculator UI on the screen using flexbox, with a background that adapts to light and dark themes, ensuring the layout takes up at least the full height of the viewport.
  • Card Component: Wraps the content with padding and a grid layout for spacing, creating a clean, centered card appearance.
  • Header: Displays the title “BMI Calculator” and a description explaining how to use the calculator.
  • Height and Weight Inputs: Provides input fields for the user to enter their height and weight, with labels and placeholders, and updates the respective state on input change.
  • Calculate Button: Triggers the calculateBmi function to compute the BMI and category.
  • Error Message: Displays any error message in red, centered within the card.
  • Result Display: Shows the calculated BMI value in a large, bold font and the corresponding category below it.

(Bonus just for you): Full Code with Comments

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

// Import necessary hooks from React
import { useState, ChangeEvent } from "react";

// Import custom UI components from the UI directory
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";

// Define a TypeScript interface for the BMI result
interface BmiResult {
bmi: string;
category: string;
}

// Default export of the BmiCalculator function
export default function BmiCalculator() {
// State hooks for managing height, weight, BMI result, and error message
const [height, setHeight] = useState<string>("");
const [weight, setWeight] = useState<string>("");
const [result, setResult] = useState<BmiResult | null>(null);
const [error, setError] = useState<string>("");

// Handler for updating height state on input change
const handleHeightChange = (e: ChangeEvent<HTMLInputElement>): void => {
setHeight(e.target.value);
};

// Handler for updating weight state on input change
const handleWeightChange = (e: ChangeEvent<HTMLInputElement>): void => {
setWeight(e.target.value);
};

// Function to calculate the BMI and determine the category
const calculateBmi = (): void => {
if (!height || !weight) {
setError("Please enter both height and weight."); // Alert if either input is empty
return;
}

const heightInMeters = parseFloat(height) / 100;
if (heightInMeters <= 0) {
setError("Height must be a positive number."); // Alert if height is not positive
return;
}

const weightInKg = parseFloat(weight);
if (weightInKg <= 0) {
setError("Weight must be a positive number."); // Alert if weight is not positive
return;
}

const bmiValue = weightInKg / (heightInMeters * heightInMeters); // Calculate the BMI value
let category = "";

if (bmiValue < 18.5) {
category = "Underweight"; // Set category based on BMI value
} else if (bmiValue >= 18.5 && bmiValue < 25) {
category = "Normal";
} else if (bmiValue >= 25 && bmiValue < 30) {
category = "Overweight";
} else {
category = "Obese";
}

setResult({ bmi: bmiValue.toFixed(1), category }); // Set the BMI result state
setError(""); // Clear any previous error message
};

// JSX return statement rendering the BMI calculator UI
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-900">
{/* Center the BMI calculator card within the screen */}
<Card className="w-full max-w-md mx-auto">
<CardHeader>
{/* Header with title and description */}
<CardTitle>BMI Calculator</CardTitle>
<CardDescription>
Enter your height and weight to calculate your BMI.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Input for height */}
<div className="grid gap-2">
<Label htmlFor="height">Height (cm)</Label>
<Input
id="height"
type="number"
placeholder="Enter your height"
value={height}
onChange={handleHeightChange}
/>
</div>
{/* Input for weight */}
<div className="grid gap-2">
<Label htmlFor="weight">Weight (kg)</Label>
<Input
id="weight"
type="number"
placeholder="Enter your weight"
value={weight}
onChange={handleWeightChange}
/>
</div>
{/* Button to calculate BMI */}
<Button onClick={calculateBmi}>Calculate</Button>
{/* Display error message if any */}
{error && <div className="text-red-500 text-center">{error}</div>}
{/* Display BMI result if available */}
{result && (
<div className="grid gap-2">
<div className="text-center text-2xl font-bold">{result.bmi}</div>
<div className="text-center text-muted-foreground">
{result.category}
</div>
</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 BmiCalculator from "@/components/bmi-calculator";

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

Running the Project

To see the bmi calculator 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 BMI Caclulator 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 explored the creation of a BMI calculator using Next.js. We covered:

  • The purpose and main features of the application.
  • A detailed breakdown of the bmi-calculator.tsx component, explaining how each part of the code works together.

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)