Skip to content

Commit 3c30ee2

Browse files
Add Body Composition Calculator play (#1672)
Co-authored-by: Priyankar Pal <[email protected]>
1 parent 06ffd2a commit 3c30ee2

File tree

7 files changed

+560
-0
lines changed

7 files changed

+560
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React, { useMemo, useState } from 'react';
2+
3+
import './styles/styles.css';
4+
import CalculatorForm from './components/CalculatorForm';
5+
import ResultDisplay from './components/ResultDisplay';
6+
7+
import { calculateBMR, calculateTDEE } from './utils/healthUtils';
8+
import { FormData } from './types';
9+
10+
const BodyCompositionCalculator: React.FC = () => {
11+
const [formData, setFormData] = useState<FormData>({
12+
gender: 'male',
13+
weight: '',
14+
height: '',
15+
age: '',
16+
activityLevel: 'sedentary'
17+
});
18+
19+
/**
20+
* Generic handler for updating form state
21+
*/
22+
const handleChange = (field: keyof FormData, value: string) => {
23+
setFormData((prev) => ({
24+
...prev,
25+
[field]: value
26+
}));
27+
};
28+
29+
/**
30+
* Convert Inputs Safely
31+
*/
32+
const numericWeight = Number(formData.weight);
33+
const numericHeight = Number(formData.height);
34+
const numericAge = Number(formData.age);
35+
36+
const isValid = numericWeight > 0 && numericHeight > 0 && numericAge > 0;
37+
38+
/**
39+
* Derived BMR
40+
*/
41+
const bmr = useMemo(() => {
42+
if (!isValid) return null;
43+
44+
return calculateBMR({
45+
gender: formData.gender,
46+
weight: numericWeight,
47+
height: numericHeight,
48+
age: numericAge
49+
});
50+
}, [formData.gender, numericWeight, numericHeight, numericAge, isValid]);
51+
52+
/**
53+
* Derived TDEE
54+
*/
55+
const tdee = useMemo(() => {
56+
if (!bmr) return null;
57+
58+
return calculateTDEE(bmr, formData.activityLevel);
59+
}, [bmr, formData.activityLevel]);
60+
61+
return (
62+
<div className="container">
63+
<h2 className="calculator-title">BMR & TDEE Calculator</h2>
64+
65+
<p className="calculator-summary">
66+
<strong>BMR (Basal Metabolic Rate)</strong> is the number of calories your body needs at
67+
complete rest to maintain essential functions such as breathing, circulation, and
68+
temperature regulation.
69+
<br />
70+
<br />
71+
<strong>TDEE (Total Daily Energy Expenditure)</strong> is the total number of calories your
72+
body burns in a day, including all physical activities like walking, exercise, and daily
73+
tasks.
74+
</p>
75+
<CalculatorForm formData={formData} onChange={handleChange} />
76+
77+
<ResultDisplay bmr={bmr} tdee={tdee} />
78+
</div>
79+
);
80+
};
81+
82+
export default BodyCompositionCalculator;
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Body Composition Calculator
2+
3+
An interactive Body Composition Calculator that computes Basal Metabolic Rate (BMR) and Total Daily Energy Expenditure (TDEE) using scientifically validated formulas.
4+
5+
This calculator helps users understand their daily calorie requirements based on body measurements and activity level.
6+
7+
---
8+
9+
## Play Demographic
10+
11+
- Language: TypeScript
12+
- Level: Intermediate
13+
- Category: Health / Fitness / Calculator
14+
15+
---
16+
17+
## Creator Information
18+
19+
- User: deansereigns
20+
- GitHub Link: https://github.com/deansereigns
21+
22+
---
23+
24+
## Implementation Details
25+
26+
This play is built using React and TypeScript with a modular and scalable architecture.
27+
28+
The calculator uses the **Mifflin-St Jeor Equation**, which is considered one of the most accurate formulas for estimating Basal Metabolic Rate.
29+
30+
### BMR (Basal Metabolic Rate)
31+
32+
BMR represents the number of calories your body requires to perform essential life-sustaining functions while at rest.
33+
34+
These include:
35+
36+
- Breathing
37+
- Blood circulation
38+
- Brain activity
39+
- Cell production
40+
- Maintaining body temperature
41+
42+
Formula used:
43+
44+
Male:
45+
46+
```text
47+
BMR = (10 × weight_kg) + (6.25 × height_cm) − (5 × age_years) + 5
48+
```
49+
50+
Female:
51+
52+
```text
53+
BMR = (10 × weight_kg) + (6.25 × height_cm) − (5 × age_years) − 161
54+
```
55+
56+
---
57+
58+
### TDEE (Total Daily Energy Expenditure)
59+
60+
TDEE represents the total calories your body burns in a day including all physical activities.
61+
62+
It is calculated by multiplying BMR with an activity multiplier.
63+
64+
Formula:
65+
```text
66+
TDEE = BMR × activity_multiplier
67+
```
68+
69+
Activity multipliers used:
70+
71+
| Activity Level | Multiplier |
72+
|---------------|------------|
73+
| Sedentary | 1.2 |
74+
| Light Activity | 1.375 |
75+
| Moderate Activity | 1.55 |
76+
| Active | 1.725 |
77+
| Very Active | 1.9 |
78+
79+
---
80+
81+
### Features Implemented
82+
83+
- Built with TypeScript for type safety
84+
- Modular and reusable component structure
85+
- Real-time BMR and TDEE calculation using React Hooks
86+
- Controlled form inputs with validation
87+
- Clean and responsive user interface
88+
89+
---
90+
91+
## Considerations
92+
93+
- Validates inputs before performing calculations
94+
- Handles empty or invalid values safely
95+
- Optimized using React Hooks
96+
- Easily extendable for additional health metrics
97+
98+
---
99+
100+
## Future Improvements
101+
102+
- BMI and body fat calculator
103+
- Calorie recommendations for weight goals
104+
- Enhanced UI and data visualization
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import React, { ChangeEvent } from 'react';
2+
import { FormData, Gender, ActivityLevel } from '../types';
3+
4+
interface CalculatorFormProps {
5+
formData: FormData;
6+
onChange: (field: keyof FormData, value: string) => void;
7+
}
8+
9+
/**
10+
* Calculator Form
11+
*
12+
* Responsible for rendering and managing all input fields.
13+
* This is a presentational component with no business logic.
14+
*/
15+
const CalculatorForm: React.FC<CalculatorFormProps> = ({ formData, onChange }) => {
16+
/**
17+
* Handle select change safely with typing
18+
*/
19+
const handleGenderChange = (event: ChangeEvent<HTMLSelectElement>) => {
20+
onChange('gender', event.target.value as Gender);
21+
};
22+
23+
const handleActivityChange = (event: ChangeEvent<HTMLSelectElement>) => {
24+
onChange('activityLevel', event.target.value as ActivityLevel);
25+
};
26+
27+
/**
28+
* Handle numeric input changes
29+
*/
30+
const handleWeightChange = (event: ChangeEvent<HTMLInputElement>) => {
31+
onChange('weight', event.target.value);
32+
};
33+
34+
const handleHeightChange = (event: ChangeEvent<HTMLInputElement>) => {
35+
onChange('height', event.target.value);
36+
};
37+
38+
const handleAgeChange = (event: ChangeEvent<HTMLInputElement>) => {
39+
onChange('age', event.target.value);
40+
};
41+
42+
return (
43+
<div className="form">
44+
{/* Gender*/}
45+
<label>
46+
Gender
47+
<select value={formData.gender} onChange={handleGenderChange}>
48+
<option value="male">Male</option>
49+
<option value="female">Female</option>
50+
</select>
51+
</label>
52+
53+
{/* Weight*/}
54+
<label>
55+
Weight (kg)
56+
<input
57+
min="40"
58+
placeholder="Enter weight"
59+
type="number"
60+
value={formData.weight}
61+
onChange={handleWeightChange}
62+
/>
63+
</label>
64+
65+
{/* Height*/}
66+
<label>
67+
Height (cm)
68+
<input
69+
min="70"
70+
placeholder="Enter Height"
71+
type="number"
72+
value={formData.height}
73+
onChange={handleHeightChange}
74+
/>
75+
</label>
76+
77+
{/* Age*/}
78+
<label>
79+
Age
80+
<input
81+
min="10"
82+
placeholder="Enter Age"
83+
type="number"
84+
value={formData.age}
85+
onChange={handleAgeChange}
86+
/>
87+
</label>
88+
89+
{/* Activity Level */}
90+
<label>
91+
Activity Level
92+
<select value={formData.activityLevel} onChange={handleActivityChange}>
93+
<option value="sedentary">Sedentary (little or no exercise)</option>
94+
<option value="light">Light (1-3 days/week)</option>
95+
<option value="moderate">Moderate (3-5 days/week)</option>
96+
<option value="active">Active (6-7 days/week)</option>
97+
<option value="very_active">Very Active (physical job or intense training)</option>
98+
</select>
99+
</label>
100+
</div>
101+
);
102+
};
103+
104+
export default CalculatorForm;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from 'react';
2+
3+
interface ResultDisplayProps {
4+
bmr: number | null;
5+
tdee: number | null;
6+
}
7+
8+
/**
9+
* ResultDisplay Component
10+
*
11+
* Displays calculated BMR and TDEE values.
12+
* Pure Presentational component.
13+
*/
14+
const ResultDisplay: React.FC<ResultDisplayProps> = ({ bmr, tdee }) => {
15+
/**
16+
* Format number Safely
17+
*/
18+
const formatValue = (value: number | null): string => {
19+
if (value === null || Number.isNaN(value)) {
20+
return '--';
21+
}
22+
23+
return `${Math.round(value)} kcal/day`;
24+
};
25+
26+
return (
27+
<div className="result-container">
28+
<h3>Results</h3>
29+
30+
<div className="result-item">
31+
<span className="label">BMR</span>
32+
<span className="value">{formatValue(bmr)}</span>
33+
</div>
34+
35+
<div className="result-item">
36+
<span className="label">TDEE</span>
37+
<span className="value">{formatValue(tdee)}</span>
38+
</div>
39+
40+
{bmr === null && <p className="hint">Enter weight, height, and age to calculate.</p>}
41+
</div>
42+
);
43+
};
44+
45+
export default ResultDisplay;

0 commit comments

Comments
 (0)