In this post, we will build a simple React app using Next.js. Nothing complicated, just a simple calculator app that will demonstrate a number of features of React.
Note that the full source code can be found at the link given at the end of this post.
The calculator will start off simple, just add, subtract, multiply and divide. Maybe we will add something later.
We'll start by getting the environment setup. For Next, you need to have node version 18 or later. I am using 20.10. Next.js will setup the default project structure for you. This is recommended, so use the following:
>npx create-next-app@latest
✔ What is your project named? … calculator
✔ Would you like to use TypeScript? … No / **Yes**
✔ Would you like to use ESLint? … No / **Yes**
✔ Would you like to use Tailwind CSS? … No / **Yes**
✔ Would you like to use `src/` directory? … **No** / Yes
✔ Would you like to use App Router? (recommended) … No / **Yes**
✔ Would you like to customize the default import alias (@/*)? … **No** / Yes
Creating a new Next.js app in /projects/react/calculator.
Using npm.
>cd calculator
There is now a basic React/Next application in the calculator folder. You can test the app using the following:
npm run dev
Now, to finish up the environment. We'll be using the ShadCN ui components, so let's install those:
npx shadcn-ui@latest init
We will be using the ShadCN button component, so install that:
npx shadcn-ui@latest add button
Before we get too far along, let's consider what the UI will look like and how it is used.
The UI will have a display that show the current number entered or the result of the latest operation. There will be ten keys, 0 to 9, the decimal key: '.', the operation keys: +. -, *, /, and, of course, the =.
First, think about how a user interacts with the calculator. In a typical use case, the user enters the first number, or operand, the operation to be performed, the second number, or operand, then presses the = key to perform the operation.
Now, consider local memory needs. The app needs to remember the first operand, the second operation, and the operation, in order to perform the calculation when the = is pressed. We will use React's 'useState' to remember what the use pressed.
Now, let's start building the calculator now.
We create the calculator component in the components folder, calculator.tsx:
"use client"
import React, { useState } from 'react';
import { Button } from "@/components/ui/button"
const Calculator = () => {
const [display, setDisplay] = useState('0');
const [firstOperand, setFirstOperand] = useState({"isSet":false,"num":0});
const [operator, setOperator] = useState("");
const [waitingForSecondOperand, setWaitingForSecondOperand] = useState(false);
...
}
export default Calculator;
At the start of the component, we have defined the local memory, or state, that is needed. There is a display that holds the most recent number entered, an object to hold the first operand and a flag designating if it has been entered, an object to hold the operator, and an object to hold a boolean telling us if the second operand has been entered. Note that when the second operand is entered it is held in the 'display' state variable, so we only need a boolean to tell us if it has been entered
Let's look at the component layout.
return (
<div className="w-64 mx-auto mt-10 bg-gray-100 rounded-lg shadow-lg p-4">
<div className="bg-white h-16 mb-4 flex items-center justify-end px-4 text-3xl font-bold rounded">
{display}
</div>
<div className="grid grid-cols-4 gap-2">
<Button onClick={() => inputDigit(7)}>7</Button>
<Button onClick={() => inputDigit(8)}>8</Button>
<Button onClick={() => inputDigit(9)}>9</Button>
<Button onClick={() => performOperation('/')}>/</Button>
<Button onClick={() => inputDigit(4)}>4</Button>
<Button onClick={() => inputDigit(5)}>5</Button>
<Button onClick={() => inputDigit(6)}>6</Button>
<Button onClick={() => performOperation('*')}>*</Button>
<Button onClick={() => inputDigit(1)}>1</Button>
<Button onClick={() => inputDigit(2)}>2</Button>
<Button onClick={() => inputDigit(3)}>3</Button>
<Button onClick={() => performOperation('-')}>-</Button>
<Button onClick={() => inputDigit(0)}>0</Button>
<Button onClick={inputDecimal}>.</Button>
<Button onClick={() => performOperation('=')}>=</Button>
<Button onClick={() => performOperation('+')}>+</Button>
<Button onClick={clear} className="col-span-4">Clear</Button>
</div>
</div>
);
That is the standard 4 rows of 4 keys of a simple calculator.
We need to update the default page.tsx file to use the component.
import Calculator from '@/components/calculator';
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-center p-24">
<h1 className="text-4xl font-bold mb-8">Calculator App</h1>
<Calculator />
</main>
);
}
This is the main page view and it simply loads the Calculator component.
In the calculator component, we defined some events. This is the main logic of the UI. When a user presses a numeric button, the component should respond by storing the number and putting it in the display. The inputDigit method handles this logic.
const inputDigit = (digit: number) => {
if (waitingForSecondOperand) {
setDisplay(String(digit));
setWaitingForSecondOperand(false);
} else {
setDisplay(display === '0' ? String(digit) : display + digit);
}
};
It's pretty simple, if the user has not entered an operation this must be a digit in the first operand in which case we need only append the digit to whatever is already in the display.
Similarly, if the component is waiting on the second operation, then we need to put the digit in the display and remove the 'waitingForSecondOperand' flag. Note that there is no problem if the second operand has multiple digits because the component expects it to be stored in the display which is handled by the 'else' case.
The inputDecimal function handles the case of the user entering a decimal point. Nothing complicated here, the logic simply appends the '.' to the number in the display.
const inputDecimal = () => {
if (!display.includes('.')) {
setDisplay(display + '.');
}
};
The performOperation handles the event when the user presses the '=' of one of the math operation keys.
const performOperation = (nextOperator: string ) => {
const inputValue = parseFloat(display);
if (!firstOperand.isSet) {
setFirstOperand({"isSet":true,"num":inputValue});
} else if (operator!="" && nextOperator==="=") {
const result = calculate(firstOperand.num, inputValue, operator);
setDisplay(String(result));
setFirstOperand({"isSet":true,"num":result});
}
setWaitingForSecondOperand(true);
setOperator(nextOperator);
};
Let's walk through the logic. If the user has entered a number then presses a math operation, say the '+' key, the firstOperand has not been 'set' as far as the component is concerned. The first operand is in the display state variable, so we need to pull it from there and set the firstOperand state variable.
Otherwise, if the key is the '=' and operation has already been given, then the calculation can take place and we put the result as the firstOperand of any followup operation.
In all cases, the component can assume that it is now in the state of having the firstOperand and is waiting for the second.
The clear function simply resets the display and all the state so the component is waiting for the first number to be entered.
const clear = () => {
setDisplay('0');
setFirstOperand({"isSet":false,"num":0});
setOperator("");
setWaitingForSecondOperand(false);
};
All this allows the calculator to handle a sequence of simple operations. For example, consider the key sequence:
1 + 5 = / 3 =
properly returns 2.
Note that this implementation will not handle more complicated sequences such as:
7 + 9 / 8 =
That is a bit more complex since it would need to follow precedence of operations rules. Note that this calculator would return 0.875 and one that was based on the programming rules of precedence, the answer would be 8.125, not 2.
This is pretty simple as calculators go, but serves to demonstrate basic React app structure.
What would you do different? How would you modify it to handle to accept parentheses to do longer calculations? Let me know your thoughts in the comments.
Thanks!
The full code for this can be found here
Top comments (0)