BMI Calculator using LWC

Deep Banerjee
6 min readApr 7, 2023

--

project-based learning series: Part 2

In this series of Project-based learning, today we’re going to see how we can build a responsive app using LWC.

Disclaimer: “ This is not a production ready code , as there’s room for improvement . This is only for educational purposes for beginners to learn real projects .”

Let’s start to build our BMI calculator:

Html code :

So, we want to create a simple UI to have weight and height as user input

and display the BMI instantly with each change. Hence we’re going to use the onchange event. The event is triggered whenever the value of the HTML element changes.

<template>
<div class="container">
<div class="card">
<div class="title">BMI Calculator</div>
<div class="input-container">
<div class="input-wrapper">
<lightning-input type="number" required=true label="Weight (kg)" value={weight} onchange={handleWeightChange}>
</lightning-input>
</div>
<div class="input-wrapper">
<lightning-input type="number" required=true label="Height (cm)" value={height} onchange={handleHeightChange}>
</lightning-input>
</div>
</div>
<div class="result-container">
<div class="result-label">BMI:</div>
<div class="result-value" style={resultValueStyle}>{bmi}</div>
</div>
<div class="category-container">
<div class="category-label">Category:</div>
<div class="category-value" style={categoryValueStyle}>{category}</div>
</div>
</div>
</div>
</template>

Also we would like to have a dynamic text color change , hence we are using the categoryValueStyle and resultValueStyle for this.

Let’s see the Js code :

import { LightningElement,track } from "lwc";

export default class App extends LightningElement {
@track weight;
@track height;

get bmi() {
if (this.weight && this.height) {
let heightInMeters = (this.height/100);
let bmi = this.weight / (heightInMeters * heightInMeters);
let finalBmi = bmi.toFixed(2);
return finalBmi;
} else {
return '';
}
}

get category() {
const bmi = parseFloat(this.bmi);
if (bmi < 18.5) {
return 'Underweight';
} else if (bmi >= 18.5 && bmi < 25) {
return 'Normal';
} else if (bmi >= 25 && bmi < 30) {
return 'Overweight';
} else {
return 'Obese';
}
}
get resultValueStyle() {
const bmi = parseFloat(this.bmi);
if (bmi < 18.5 || bmi >= 30) {
return 'color: red';
} else if (bmi >= 18.5 && bmi < 25) {
return 'color: green';
} else if (bmi >= 25 && bmi < 30) {
return 'color: yellow';
}
}

get categoryValueStyle() {
const bmi = parseFloat(this.bmi);
if (bmi < 18.5 || bmi >= 30) {
return 'color: red';
} else if (bmi >= 18.5 && bmi < 25) {
return 'color: green';
} else if (bmi >= 25 && bmi < 30) {
return 'color: yellow';
}
}

handleWeightChange(event) {
this.weight = parseFloat(event.target.value);
}

handleHeightChange(event) {
this.height = parseFloat(event.target.value);
}

calculateBMI() {
if (this.weight > 0 && this.height > 0) {
let heightInMeters = (this.height/100);
let finalBmi = this.weight / (heightInMeters * heightInMeters);
this.bmi = finalBmi.toFixed(2);
}
}
}

Now , let’s try to break the JS code and understand it

  @track weight;
@track height;

The track decorator marks this property as reactive, so changes to their values will automatically update the component’s UI.

 get bmi() {
if (this.weight && this.height) {
let heightInMeters = (this.height/100);
let bmi = this.weight / (heightInMeters * heightInMeters);
let finalBmi = bmi.toFixed(2);
return finalBmi;
} else {
return '';
}
}

This is a getter that takes two inputs (weight and height) and calculates the BMI and returns the value upto two decimal places

it will return an empty string if either of the input is null. Though this else statement can be avoided as the fields are marked required, so the values will never be null. But you may never know how brilliant an end user might be 😆

  get resultValueStyle() {
const bmi = parseFloat(this.bmi);
if (bmi < 18.5 || bmi >= 30) {
return 'color: red';
} else if (bmi >= 18.5 && bmi < 25) {
return 'color: green';
} else if (bmi >= 25 && bmi < 30) {
return 'color: yellow';
}
}

This is a getter method that calculates the CSS style for the result-value div based on the BMI value. It returns a string value that sets the color property to 'red', 'green', or 'yellow' depending on the BMI category.

  get categoryValueStyle() {
const bmi = parseFloat(this.bmi);
if (bmi < 18.5 || bmi >= 30) {
return 'color: red';
} else if (bmi >= 18.5 && bmi < 25) {
return 'color: green';
} else if (bmi >= 25 && bmi < 30) {
return 'color: yellow';
}
}

This is a getter method that calculates the CSS style for the category-value div based on the BMI value. It returns a string value that sets the color property to 'red', 'green', or 'yellow' depending on the BMI category.

and finally

  handleWeightChange(event) {
this.weight = parseFloat(event.target.value);
}

handleHeightChange(event) {
this.height = parseFloat(event.target.value);
}

These two methods update the weight and height properties with the new values entered by the user in the input fields. They use the parseFloat function to convert the input values to numbers. In general, HTML inputs are strings by default.

Now, the last but not the least is to make this component responsive , so that it works on all devices ( desktop, mobile and tablets).

for this let’s see the CSS :

.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}

.card {
background: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(5px);
border-radius: 10px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.25);
padding: 20px;
width: 100%;
max-width: 400px;
}

.title {
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
text-align: center;
}

.input-container {
display: flex;
flex-direction: column;
margin-bottom: 20px;
}

.input-wrapper {
margin-bottom: 10px;
}

.result-container {
display: flex;
align-items: center;
}

.result-label {
font-size: 18px;
font-weight: bold;
margin-right: 10px;
}

.result-value {
font-size: 24px;
font-weight: bold;
}

@media only screen and (max-width: 767px) {
.card {
max-width: 90%;
}
}

This rule applies to the container class, which is the main container for the BMI calculator component. It sets the container to use flexbox layout, with the flex-direction property set to column. This means that child elements will be stacked vertically inside the container. The align-items and justify-content properties are set to center, which centers the child elements both horizontally and vertically. The height property is set to 100vh, which makes the container take up the full height of the viewport.

The @media rule in CSS is used to define different styles for different media types or screen sizes. With the @media rule, you can create responsive designs that adapt to the screen size of the device, such as desktop, tablet, or mobile phone.

The syntax for using the @media rule is as follows:

@media media-type and (media-feature-rule) {
/* CSS rules go here */
}

Here’s what each part of the syntax means:

  • media-type: This specifies the type of media the rule applies to, such as screen for computer screens or print for printed pages.
  • media-feature-rule: This specifies one or more conditions that must be true for the rule to apply. Examples of media features include min-width, max-width, orientation, and resolution.
  • CSS rules: This is the set of CSS rules that will apply to the specified media type and media feature(s).

OUTPUT:

Desktop view

On Mobile view :

Tablet view :

More outputs…

For those who are interested, here’s part 1 of the series

Thanks for reading!

--

--