Bill and Discount Calculator

In this example project we are calculating total tip and sharing based on selected percentage of discount. As user enter total bill amount, discount get calculated based on selected %5 or %10 or any.


Prepare Project for Bill and Discount Calculator

To create the example project for this example, open command prompt, navigate to a convenient location, and run the command as shown below :

create-react-app example16

src\App.js
import React from 'react';
import Calculator from './Calculator';
import './App.css';

function App() {
  return (
    <Calculator />
  );
}

export default App;

src\Calculator.js
import React, { Component } from 'react';

import Results from './Results';
import Inputs from './Inputs';
import Buttons from './Buttons';
const btnsValue = [9,8,7,6,5,4,3,2,1,'.',0,'DEL'];
const tipPercentages = [.05, .10, .15, .20];

class Calculator extends Component {
	constructor(props) {
		super(props);
		this.state = {
			buttons: btnsValue,
			clickedBtn: '',
			billTotal: '',
			numberOfPeople: 1,
			percentages: tipPercentages,
			tipPercent: tipPercentages[0],
			tipTotal: 0,
			costPP: 0
		};

		this.onClickButton = this.onClickButton.bind(this);
		this.updateBillTotal = this.updateBillTotal.bind(this);
		this.updatePartyCount = this.updatePartyCount.bind(this);
		this.getTipPercentage = this.getTipPercentage.bind(this);
	}

	onClickButton(i) {			
		this.setState({
			clickedBtn: this.state.buttons[i]
		}, function() {
			this.updateBillTotal(i);
		});	
	}

	updateBillTotal(i) {
		let newState;

		if(this.state.clickedBtn === '.' && this.state.billTotal.includes('.')) {		
			return null;
		}  

		if(this.state.clickedBtn !== 'DEL') {
			newState = this.state.billTotal + this.state.clickedBtn;
			this.setState({
				billTotal: newState
			}, function() {
				this.calculateCosts();
			}
			);
		} else{
			newState = '';
			this.setState({
				billTotal: newState,
				numberOfPeople: 2,
				tipTotal: 0,
				costPP: 0
			}, function() {
				this.calculateCosts();
			});
		}
	}


	updatePartyCount(sum) {
		let newState;
		if(sum === 'add') {
			newState = this.state.numberOfPeople + 1;
			this.setState({
				numberOfPeople: newState
			}, function() {
			this.calculateCosts();
		});
		} else if(sum === 'minus' && this.state.numberOfPeople > 1) {
			newState = this.state.numberOfPeople - 1;
			this.setState({
				numberOfPeople: newState
			}, function() {
				this.calculateCosts();
			});
		} else {
			this.setState({
				numberOfPeople: this.state.numberOfPeople
				}, function() {
				this.calculateCosts();
			});
		}
	}

	getTipPercentage(i) {
		let newState = this.state.percentages[i];
		this.setState({
			tipPercent: newState
		}, function() {
			this.calculateCosts();
		}
		);
	}

	calculateCosts() {
		let newBillTotal = parseFloat(this.state.billTotal);
		if(!Number.isNaN(newBillTotal)) {
			let newTipTotal, newCostPP;
			newTipTotal = parseFloat(newBillTotal * this.state.tipPercent);
			newCostPP = newBillTotal + newTipTotal; 
			newCostPP = newCostPP / this.state.numberOfPeople;
	    this.setState({
			tipTotal: newTipTotal,
			costPP: newCostPP
		});
		}
	}

	render() {
		return (
			<div>
				<Results 
					costPerPerson={this.state.costPP} 
					billTotal={this.state.billTotal}
					tipTotal={this.state.tipTotal}
					partyCount={this.state.numberOfPeople} />
					<Inputs 
					billTotal={this.state.billTotal} 
					tipTotal={this.state.tipTotal}
					getPartyCount={this.updatePartyCount} 
					partyCount={this.state.numberOfPeople} 
					getTipPercentage={this.getTipPercentage}
					handleInputChange={this.handleInputChange} />
				<Buttons onClickButton={this.onClickButton} buttons={this.state.buttons} />
			</div>
		)
	}
}

export default Calculator;

src\Buttons.js
import React from 'react';

const Buttons = (props) => {
	return (
		<ul className="flex-row keypad">
			{props.buttons.map((btn,i) => {
				return (
					<li key={`btn-${i}`}>
						<button onClick={e => props.onClickButton(i)}>{btn}</button>
					</li>
				)
			})}
		</ul>
	);
}

export default Buttons;

src\Inputs.js
import React from 'react';
import { Component } from 'react';

const tipPercentages = ['5%', '10%', '15%' , '20%'];

class Inputs extends Component {
	constructor(props) {
		super(props);
		this.state = {
			active: 0,
			keyInput: ''
		};
	}

	handleStyleChange(i) {
		if(this.state.active === i) {
			return 'selected-tipPerc';
		} else {
			return '';
		}
	}

	handleTipSelect(i) {
		this.setState({ active: i });
		this.props.getTipPercentage(i)
	}

	render() {
		return (
			<div className="inputs flex-row">
				<div className="bill-total flex-col">
					<span>Bill Total</span>
					<input type="text" defaultValue={this.props.billTotal} onKeyPress={(e) => this.handleKeyPress(e)}  disabled />
				</div>
				<div className="guest-count flex-row">
					<div onClick={() => this.props.getPartyCount('minus')}>
						<i className="icon ion-md-remove"></i>
					</div>
					<div>
						<span>{this.props.partyCount} </span>
						<i className="icon ion-md-person"></i>
					</div>
					<div onClick={() => this.props.getPartyCount('add')}>
						<i className="icon ion-md-add"></i>
					</div>
				</div>
				<div className="tip-percent flex-col">
					<ul>
						{
							tipPercentages.map((el, i) => (
								<li className={this.handleStyleChange(i)} key={el} onClick={() => this.handleTipSelect(i)}>
									{el}
								</li>
							))
						}
					</ul>
				</div>
				<div className="tip-total flex-col">
					<span>Tip Total </span>
					<span>$ {this.props.tipTotal.toFixed(2)}</span>	
				</div>	
			</div>
		)

	}
}
export default Inputs;

src\Results.js
import React from 'react';

const Results = (props) => {
	let tipTotal = parseFloat(props.tipTotal);
	let billTotal = parseFloat(props.billTotal
		);
	if (isNaN(billTotal)) {
		billTotal = '';
	}
	let partyCount = props.partyCount;
	return (
		<ul className="results flex-col">
			<li className="cost-pp flex-col">
				<span>You owe</span>
				<span>$ {props.costPerPerson.toFixed(1)}</span>
			</li>
			<li className="col-2">
				<span>Bill</span>
				<span>$ </span><span>{(billTotal / partyCount).toFixed(1)}</span>
			</li>
			<li className="col-2">
				<span>Tip  </span>
				<span>$ </span><span>{(tipTotal / partyCount).toFixed(1)}</span>
			</li>
		</ul>
	)
}

export default Results;

src\App.css

Replace the placeholder content of App.css with given below content :

.App {
  text-align: center;
}

.App-logo {
  animation: App-logo-spin infinite 20s linear;
  height: 40vmin;
  pointer-events: none;
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

body {
	margin: 0;
	padding: 0;
	background-color: #007bff ;
	font-family: 'Raleway', sans-serif;
}

#root {
	width: 300px;
	margin: 0 auto;
	background-color: #52BCAA;
	border-radius: 20px;
	padding: 10px;
}

.flex-row {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
}

.flex-col {
	display: flex;
	flex-direction: column;
}

.col-2 {
	width: 50%;
}

ul {
	list-style: none;
	padding: 0;
	width: 100%;
}

.results {
	background-color: #fff;
	width: 80%;
	color: #8FD7CA;
	height: 191px;
	justify-content: space-evenly;
	padding-left: 30px;
    padding-right: 30px;
}

.cost-pp {
	width: 100%;
	text-align: right;
	display: 
}


/*INPUTS*/
.bill-total, 
.guest-count,
.tip-percent, 
.tip-total {
	width: 48%;
	color: #FDFEFD;
	background-color: #8FD7CA;
	border-radius: 10px;
	margin-bottom: 10px;
}

.bill-total {
	justify-content: center;
	align-items: center;
	padding-top: 10px;
	padding-bottom: 10px;
}

.bill-total input {
	background-color: #8FD7CA;
	color: #FDFEFD;
	border:none;
	width: 60%;
	text-align: center;
	height: 20px;
}

.bill-total input:focus {
	outline: none;
}


.guest-count {
	align-items: center;
	justify-content: space-evenly;
}

.guest-count i {
	cursor: pointer;
}

.guest-count div:nth-child(2) {
	background-color: #FDFEFD;
	color: #8FD7CA;
	height: 60px;
	width: 60px;
	border-radius: 60px;
	display: flex;
	justify-content: center;
	align-items: center;
}

.selected-tipPerc {
	background-color: #fff;
	color: #8FD7CA;
}

.tip-percent {
	width: 65%;
	justify-content: center;
	align-items: center;
	/*margin: 0;*/
}

.tip-percent ul {
	display: flex;
	width: 100%;
	height: 100%;
	margin: 0;
	border-radius: 10px;
}

.tip-percent ul li {
	height: 100%;
	cursor: pointer;
	width: 25%;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 10px;
}

.tip-total {
	width: 30%;
	height: 70px;
	justify-content: center;
	text-align: center;
}


/*KEYPAD*/
.keypad li {
	width: 32%;
	cursor: pointer;
}
.keypad li button {
	width: 100%;
	height: 40px;
	background-color: #52BCAA;
	color: #FDFEFD;
	border: 1px solid #fff;
	cursor: pointer;
	margin: 5px 0;
	font-size: 1.5em;
}

.keypad li button:focus {
	outline: none;
}


Most Helpful This Week