Getting Started
To properly follow this material, you must have installed the latest NPM and Node.JS:
To start with, let's create our project directory. Let's call it bmi-calculator
Create the project directory structure as illustrated below.
bmi-calculator/
public/
index.html
src/
index.js
package.json
Now place the following code on your index.html file.
<!DOCTYPE html>
<html>
<head>
<title>BMI Calculator</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Then let's add dependencies in our package.json by importing react, react-dom, and react-scripts.
{
"name": "bmi-calculator",
"version": "1.0.0",
"description": "BMI Calculator",
"main": "index.js",
"dependencies": {
"react": "16.2.0",
"react-dom": "16.2.0",
"react-scripts": "1.1.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
Then execute the command npm install using your command line terminal in the same directory where our package.json is located. This should install the dependencies we have added in our package.json file.
Creating components
Now let's edit our index.js file and add the following:
import React from 'react';
import ReactDOM from 'react-dom';
import Form from './components/Form.js';
ReactDOM.render(<Form />, document.getElementById("root"));
In our code above, we are telling React to take our Form component and render it to the element with an ID of root. But the Form component is still missing and we still need to create it. To do that, create a folder inside src , name it as components , this folder will contain all of our components.
Then let's create a file named Form.js inside components directory and add the following code:
import React from 'react';
class Form extends React.Component {
render() {
return(
<div></div>
)
}
}
export default Form;
Now this component still does not do much since it only returns an empty div. Let's create another component and name it as TextInput . Create a new JS file named TextInput.js and place the following code on it:
import React from 'react';
class TextInput extends React.Component {
render() {
return(
<div>
<label>Sample label</label>
<input type="text" placeholder="Placeholder" />
</div>
)
}
}
export default TextInput;
As you can see above, our TextInput component is a combination of label and input tag. Please also take note that for now, we will be using static values for the label and placeholder.
Now let's add the TextInput component in our Form component. But how should we do this? By default, every single JavaScript file is invisible to each other so our Form component cannot see our TextInput component. In order for us to use the TextInput component on our Form component, we need to use the import statement.
Add this line of code in our Form component
import TextInput from './TextInput.js';
Then add our TextInput component inside the render method of our Form component. Our Form component should look something like this:
import React from 'react';
import TextInput from './TextInput.js';
class Form extends React.Component {
render() {
return(
<div>
<TextInput />
</div>
)
}
}
export default Form;
Now that we have our Form, which is a container for the other components, a TextInput which accepts user inputs, we now need to create our Button component. Create a new JS file inside our components directory and name it as Button.js. Place the following code inside Button.js:
import React from 'react';
class Button extends React.Component {
render() {
return(
<div></div>
)
}
}
export default Button;
We will be modifying our Button component more later. For now, we could just return an empty div.
Then we can run our app by executing the command npm start in our command line terminal. On the browser, you should have a label "Sample page" with a text input below it.
Styling Components
Now we want to customize the look of our components, font sizes and text colors. To do this, we will be using CSS for our components. Let's create a directory called "styles" inside our "src" folder. This should contain all of our component styles. Create a file named Form.css inside styles directory.
Our project structure should look like these by now:
bmi-calculator/
public/
index.html
src/
components/
Button.js
Form.js
TextInput.js
styles/
Form.css
index.js
package.json
Let's modify our Form component so we can have 2 inputs (one for weight, and the other one for the height)
render() {
return(
<div>
<div className="row">
<TextInput />
</div>
<div className="row">
<TextInput />
</div>
</div>
)
}
Here, we have added CSS classes to our components. Edit your Form.css, so we can add style rules using the specified classnames for the div.
.row {
display: block;
width: 50%;
margin: 0 auto;
padding: 20px;
box-sizing: border-box;
}
Now this makes the TextInput component centered on each row. But wait, our CSS would still not be applied on the components unless we import those CSS files in the Form component. To do this, place this import statement together with the existing import statements of the Form component.
import '../styles/Form.css';
You could also use inline-styles instead of CSS classes if you don't want to have external CSS files. An example is shown below:
<div style={{ display: 'block', width: '50%', margin: '0 auto', padding: '20px', boxSizing: 'border-box', }}>
<h2>Welcome to our BMI Calculator!</h2>
</div>
According to React JS docs, "The style attribute accepts a JavaScript object with camelCased properties rather than a CSS string."
Using props
Now we want our TextInput to be dynamic, so we want it to have different labels and placeholder values for each time it is used. To do this, modify the TextInput component and replace the following code:
render() {
return(
<div>
<label>Sample label</label>
<input type="text" placeholder="Placeholder" />
</div>
)
}
with this:
render() {
return(
<div>
<label>{ this.props.label }</label>
<input type="text" placeholder={this.props.placeholder} />
</div>
)
}
As you can see above, we have used props (short for properties) for our placeholder and label. This allows us to create a TextInput component that can be customised with a new placeholder and label every time we use it. We can specify the placeholder and label for our TextInput components in our Form component by doing it as shown below:
render() {
return(
<div>
<div className="row">
<TextInput label="Height" placeholder="Enter height in meters" />
</div>
<div className="row">
<TextInput label="Weight" placeholder="Enter weight in kg" />
</div>
</div>
)
}
Using state and handling events
Using state allows components to dynamically change output over time in response to certain events. In our TextInput component, set the value attribute of our input tag and the onChange event as shown below:
<input type="text" value={this.state.value} placeholder={this.props.placeholder} onChange={this.handleChange} />
Then still in our TextInput component, create a method called handleChange and add the following code:
handleChange(event) {
let inputValue = event.target.value;
this.setState({ value : inputValue });
this.props.onChange(inputValue);
}
and add a constructor as shown below:
constructor(props) {
super(props);
this.state = { value : '' };
// binding of 'this' in our constructor to our method handleChange is necessary for 'this' to work in handleChange method
this.handleChange = this.handleChange.bind(this);
}
NOTE: handleChange runs on every keystroke (since it is being triggered by onChange event) to update the state.
Now a quick explanation on what we have added. In our handleChange method, the program will assign the value of our input tag to our variable inputValue. Then it will call setState with an object containing the value. It will trigger then the onChange props (we will set this later in our Form component) passing the value of our input tag as a parameter to the onChange prop. The displayed value in our input tag will update as the user types into the TextInput since the value attribute is set on our input tag.
In our TextInput component, we want our state "value" to have default value, you can modify our TextInput component constructor and add this:
this.state = { value : '' };
Modify our Form component so we can have those onChange prop set. To do this, add the onChange prop on our TextInput tag as shown below:
render() {
return(
<div>
<div className="row">
<TextInput label="Height" placeholder="Enter height in meters" onChange={this.heightChange} />
</div>
<div className="row">
<TextInput label="Weight" placeholder="Enter weight in kg" onChange={this.weightChange} />
</div>
</div>
)
}
Then create the necessary methods needed and add the binding of this to those methods created in our constructor.
constructor(props) {
super(props);
this.weightChange = this.weightChange.bind(this);
this.heightChange = this.heightChange.bind(this);
}
weightChanged(weightValue) {
this.setState({ weight : weightValue });
}
heightChanged(heightValue) {
this.setState({ height: heightValue });
}
Everytime the user types in the height or weight input, the handleChange method of the TextInput will be triggered, then the handleChange method will now call the onChange prop set in our TextInput component, thus it will now call the methods set (weightChanged and heightChanged) passing the input's tag value on each method. Then on those 2 methods, each value will be set to our state with their respective keys.
Now add our Button component in our Form component by placing it below the 2 inputs:
render() {
return(
<div>
<div className="row">
<TextInput label="Height" placeholder="Enter height in meters" onChange={this.heightChange} />
</div>
<div className="row">
<TextInput label="Weight" placeholder="Enter weight in kg" onChange={this.weightChange} />
</div>
<div className="row">
<Button label="SUBMIT" onClick={ this.computeBmi } />
</div>
</div>
)
}
Modify our Button component so we can have a dynamic label, and a prop onClick that will be triggered by the onClick event
render() {
return(
<div onClick={this.props.onClick}>
{this.props.label}
</div>
)
}
Going back in our Form component, create the method computeBmi and place your formula on computing the actual bmi given the weight and height:
computeBmi() {
let bmiValue = ( this.state.weight / this.state.height) / this.state.height;
this.setState({ bmi : bmiValue });
let bmiClass = this.getBmi(bmiValue);
this.setState({ bmiClass : bmiClass });
}
As you can notice, we have called a method getBmi, this method will handle the computed bmi value and return the user's bmi classification accordingly. Create this method in our Form component and add the following:
getBmi(bmi) {
if(bmi < 18.5) {
return "Underweight";
}
if(bmi >= 18.5 && bmi < 24.9) {
return "Normal weight";
}
if(bmi >= 25 && bmi < 29.9) {
return "Overweight";
}
if(bmi >= 30) {
return "Obesity";
}
}
NOTE: Add necessary binding for the getBmi and computeBmi methods in Form component constructor.
On our computeBmi method, we have accessed the previously declared states (weight and height) and used it accordingly. Then we have set a new state object ('bmi') with it's value for us to display the actual results to the user.
Modify our Form component so we have displays for our bmi and bmi class results. Place the following code after our button component:
<div className="row">
<h3>BMI = {this.state.bmi}</h3>
</div>
<div className="row">
<h3>{this.state.bmiClass}</h3>
</div>
Now that all the necessary data has been displayed, you can now check the final output in your browser to test it. You can also continue to play with the CSS to change the appearance of our components.