Let's build a react calculator app:
The source code of complete project is in this repository https://github.com/abhishek-singh77/reactCalcy and the working demo is here https://abhishek-singh77.github.io/reactCalcy/ .
Let's start...
Go to your working directory in which you want this app. Now open terminal, if you don't have terminal then open VScode and open terminal in it. But before make sure you've installed nodeJs installed in your system.
Once the terminal is opened type -
npx create-react-app reactcalcy && cd reactcalcy
Now wait till it install the Dev environment. After, Delete all code from src/App.css and also remove this code
import logo from './logo.svg';
in src/App.js.
Your App.js file should look like this:
import React from "react";
import KeypadNew from "./KeypadNew";
import Display from "./Display";
function App() {
return (
<main>
<div id="calculator">
<Display />
<KeypadNew />
</div>
</main>
);
}
export default App;
Now let's see how our index.js file should look like
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { AppProvider } from "./Context";
import "bootstrap/dist/css/bootstrap.min.css";
ReactDOM.render(
<React.StrictMode>
<AppProvider>
<App />
</AppProvider>
</React.StrictMode>,
document.getElementById("root")
);
Create four new files in the src folder: Named as Context.js, Data.js, Display.js, KeypadNew.js.
Now in Context.js type: This will contain most of the operation performed by calculator and all the major function will be in this file.
import React, { useContext, useState, useEffect } from "react";
import * as math from "mathjs";
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [equationDisplay, setEquationDisplay] = useState("");
const [calculatedResult, setCalculatedResult] = useState("0");
const [prevCalculation, setPrevCalculation] = useState([]);
const [historyIndex, setHistoryIndex] = useState(null);
const [equals, setEquals] = useState(null);
const isOperator = new RegExp(/[-,+,*,\/]/);
const isNumber = new RegExp(/[0-9]/);
const backspace = () => {
setEquationDisplay((val) => val.substr(0, val.length - 1));
setCalculatedResult((val) => val.substr(0, val.length - 1));
};
const historyBack = () => {
if (historyIndex !== null && historyIndex >= 0) {
clearInput();
setEquals(null);
setEquationDisplay(prevCalculation[historyIndex]);
if (historyIndex > 0) {
setHistoryIndex(historyIndex - 1);
}
}
};
const historyForward = () => {
if (
historyIndex !== null &&
historyIndex < prevCalculation.length - 1
) {
clearInput();
setEquals(null);
setEquationDisplay(prevCalculation[historyIndex + 1]);
setHistoryIndex(historyIndex + 1);
}
};
const handleKeyPress = (e) => {
if (isNumber.test(e.key)) {
e.preventDefault();
handleNumbers(e.key);
} else if (isOperator.test(e.key)) {
e.preventDefault();
handleOperators(e.key);
} else if (e.key === "Backspace") {
e.preventDefault();
backspace();
} else if (e.key === "Enter") {
e.preventDefault();
calculate();
} else if (e.key === "Delete") {
e.preventDefault();
clearInput();
} else if (e.key === "ArrowUp") {
e.preventDefault();
historyBack();
} else if (e.key === "ArrowDown") {
e.preventDefault();
historyForward();
} else if (e.key === ".") {
e.preventDefault();
handleDecimal();
}
};
const handleClick = (value) => {
if (value === ".") {
handleDecimal();
}
if (value === "backspace") {
backspace();
}
if (value === "=") {
calculate();
}
if (value === "AC") {
clearInput();
}
if (isOperator.test(value)) {
handleOperators(value);
}
if (isNumber.test(value)) {
handleNumbers(value);
}
};
const handleNumbers = (value) => {
if (equals !== null) {
setEquals(null);
setCalculatedResult("");
setEquationDisplay("");
}
if (
calculatedResult.length === 1 &&
calculatedResult.charAt(0) === "0"
) {
setCalculatedResult(value);
} else {
setCalculatedResult((val) => (val += value));
}
if (isOperator.test(calculatedResult)) {
setCalculatedResult(value);
}
setEquationDisplay((val) => (val += value));
};
const handleOperators = (value) => {
const a = equationDisplay.charAt(equationDisplay.length - 1);
const b = equationDisplay.charAt(equationDisplay.length - 2);
if (equationDisplay === "" && value !== "-") return;
if (
equationDisplay.length === 1 &&
isOperator.test(equationDisplay.charAt(0))
) {
return;
}
if (equals !== null) {
setEquationDisplay(`${equals}${value}`);
setEquals(null);
} else if (isOperator.test(a)) {
if (value === "-" && isNumber.test(b)) {
setEquationDisplay((val) => (val += value));
} else if (isOperator.test(b)) {
setEquationDisplay(
(val) => val.substr(0, val.length - 2) + value
);
} else {
setEquationDisplay(
(val) => val.substr(0, val.length - 1) + value
);
}
} else {
setEquationDisplay((val) => (val += value));
}
setCalculatedResult(value);
};
const handleDecimal = () => {
if (!calculatedResult.includes(".")) {
if (!equationDisplay) {
setEquationDisplay("0");
}
setEquationDisplay((val) => (val += "."));
setCalculatedResult((val) => (val += "."));
}
};
const calculate = () => {
if (equals === null) {
try {
const result = math.string(math.evaluate(equationDisplay));
setCalculatedResult(result);
setEquationDisplay((val) => (val += `=${result}`));
setEquals(result);
setPrevCalculation((prevState) => [
...prevState,
equationDisplay,
]);
} catch (error) {
console.log(error);
}
}
};
const clearInput = () => {
setEquationDisplay("");
setCalculatedResult("0");
};
useEffect(() => {
setHistoryIndex(prevCalculation.length - 1);
}, [prevCalculation]);
return (
<AppContext.Provider
value={{
handleClick,
handleNumbers,
handleOperators,
handleKeyPress,
clearInput,
calculate,
equationDisplay,
calculatedResult,
setCalculatedResult,
}}
>
{children}
</AppContext.Provider>
);
};
export { AppContext, AppProvider };
export const useGlobalContext = () => {
return useContext(AppContext);
};
Now in Data.js: It is designed for the structure of the calculator and what represents what,
import { BsArrowLeft } from "react-icons/bs";
const Data = [
{ id: "clear", value: "AC", display: "AC" },
{
id: "backspace",
value: "backspace",
display: <BsArrowLeft aria-hidden={true} focusable={false} />,
},
{ id: "divide", value: "/", display: "/" },
{ id: "seven", value: "7", display: "7" },
{ id: "eight", value: "8", display: "8" },
{ id: "nine", value: "9", display: "9" },
{ id: "multiply", value: "*", display: "*" },
{ id: "four", value: "4", display: "4" },
{ id: "five", value: "5", display: "5" },
{ id: "six", value: "6", display: "6" },
{ id: "subtract", value: "-", display: "-" },
{ id: "one", value: "1", display: "1" },
{ id: "two", value: "2", display: "2" },
{ id: "three", value: "3", display: "3" },
{ id: "add", value: "+", display: "+" },
{ id: "zero", value: "0", display: "0" },
{ id: "decimal", value: ".", display: "." },
{ id: "equals", value: "=", display: "=" },
{ id: "up", value: "up", display: "up" },
];
export default Data;
Now in Display.js: This file will contain the Display part of the calculator in which all the equation are present when you press the buttons of the calculator .
import { BsArrowLeft } from "react-icons/bs";
const Data = [
{ id: "clear", value: "AC", display: "AC" },
{
id: "backspace",
value: "backspace",
display: <BsArrowLeft aria-hidden={true} focusable={false} />,
},
{ id: "divide", value: "/", display: "/" },
{ id: "seven", value: "7", display: "7" },
{ id: "eight", value: "8", display: "8" },
{ id: "nine", value: "9", display: "9" },
{ id: "multiply", value: "*", display: "*" },
{ id: "four", value: "4", display: "4" },
{ id: "five", value: "5", display: "5" },
{ id: "six", value: "6", display: "6" },
{ id: "subtract", value: "-", display: "-" },
{ id: "one", value: "1", display: "1" },
{ id: "two", value: "2", display: "2" },
{ id: "three", value: "3", display: "3" },
{ id: "add", value: "+", display: "+" },
{ id: "zero", value: "0", display: "0" },
{ id: "decimal", value: ".", display: "." },
{ id: "equals", value: "=", display: "=" },
{ id: "up", value: "up", display: "up" },
];
export default Data;
Finally the KeypadNew.js file: This will have the UI of the keypads of the calculator and functionality of button
import React, { useEffect } from "react";
import Data from "./Data";
import { useGlobalContext } from "./Context";
const KeypadNew = () => {
const { handleClick, handleKeyPress } = useGlobalContext();
useEffect(() => {
document.addEventListener("keydown", handleKeyPress);
return () => {
document.removeEventListener("keydown", handleKeyPress);
};
}, [handleKeyPress]);
return (
<section id="keypad">
{Data.map((btn) => {
const { id, value, display } = btn;
return (
<button key={id} id={id} onClick={() => handleClick(value)}>
{display}
</button>
);
})}
</section>
);
};
export default KeypadNew;
At last our css for the file :
/* variables */
:root {
--operator-color: rgb(221, 132, 81);
--number-color: rgb(226, 226, 226);
--big-number-color: rgb(197, 197, 197);
--on-hover-number-color: blue;
--on-hover-operator-color: rgb(243, 188, 156);
}
/* root */
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
#root {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vw;
background: linear-gradient(
140deg,
rgb(245, 239, 159) 10%,
rgb(117, 240, 211) 50%,
rgb(101, 189, 224) 80%
);
font-family: "Titillium Web", sans-serif;
font-size: 1.6rem;
}
/* calculator container */
#calculator {
display: flex;
flex-direction: column;
box-shadow: 0px 5px 40px 1px rgb(56, 56, 56);
width: 380px;
height: 560px;
border-radius: 18px;
}
/* calculator display */
.display {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
background: rgb(35, 36, 53);
background: linear-gradient(
140deg,
rgba(35, 36, 53, 1) 0%,
rgba(55, 44, 102, 1) 100%
);
border-top-left-radius: 18px;
border-top-right-radius: 18px;
color: rgb(195, 195, 195);
height: 30%;
width: 100%;
padding: 25px;
}
#input {
margin: 0;
text-align: right;
height: 40px;
font-size: 1.4rem;
word-break: break-all;
}
#display {
margin: 0;
text-align: right;
font-size: 4.5rem;
}
/* calculator keypad */
#keypad {
width: 100%;
height: 70%;
}
/* calculator buttons */
button {
height: 20%;
width: 25%;
border: rgb(124, 124, 124) 1px solid;
outline: none;
background-color: var(--number-color);
}
button:hover {
background-color: white;
border: 2px solid var(--on-hover-number-color);
color: var(--on-hover-number-color);
font-weight: 1000;
}
button:focus {
border: var(--on-hover-number-color) 2px solid;
}
button:active {
border: none;
}
#clear {
width: 50%;
background-color: var(--big-number-color);
}
#backspace {
background-color: var(--big-number-color);
}
#clear:hover,
#backspace:hover {
background-color: darkgray;
color: white;
font-weight: 1000;
}
#zero {
width: 50%;
border-bottom-left-radius: 18px;
}
#divide {
background-color: var(--operator-color);
}
#multiply {
background-color: var(--operator-color);
}
#subtract {
background-color: var(--operator-color);
}
#add {
background-color: var(--operator-color);
}
#equals {
border-bottom-right-radius: 18px;
background-color: rgb(218, 109, 47);
}
#divide:hover,
#multiply:hover,
#subtract:hover,
#add:hover,
#equals:hover {
background-color: var(--on-hover-operator-color);
color: black;
}
#up {
clip: rect(0 0 0 0);
clip-path: inset(100%);
height: 1px;
width: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
}
@media screen and (max-width: 480px) {
html,
body {
overflow-x: hidden;
overflow-y: hidden;
}
body {
position: relative;
}
#calculator {
min-width: 350px;
width: 100vw;
max-width: 450px;
min-height: 500px;
height: 100vh;
max-height: 700px;
border-radius: 0;
}
.display {
border-radius: 0;
}
#keypad {
border-radius: 0;
}
#zero,
#equals {
border-radius: 0;
}
}
@media screen and (max-height: 600px) {
html,
body {
overflow-x: hidden;
overflow-y: hidden;
}
body {
position: relative;
}
#calculator {
min-width: 350px;
width: 100vw;
max-width: 450px;
min-height: 500px;
height: 100vh;
max-height: 700px;
border-radius: 0;
}
.display {
border-radius: 0;
}
#keypad {
border-radius: 0;
}
#zero,
#equals {
border-radius: 0;
}
}
And that's it, type npm start in the terminal and the project will work.
Make sure you've these links are present in the index.html file
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Titillium+Web:wght@300;400&display=swap"
rel="stylesheet"
/>
For any query comment on the post.
0 Comments