Build a Next.js Interactive Web-Based Simple Calculator

Written by

in

In the digital age, a calculator is more than just a tool; it’s an essential utility. From simple arithmetic to complex scientific calculations, we rely on calculators daily. While dedicated calculator apps exist, there’s a growing need for accessible, customizable, and web-based solutions. This tutorial will guide you through building a simple, yet functional, calculator using Next.js, a powerful React framework for building modern web applications. Whether you’re a beginner eager to learn or an intermediate developer looking to hone your skills, this project provides a hands-on opportunity to understand core concepts of Next.js, React, and JavaScript.

Why Build a Web-Based Calculator?

Creating a web-based calculator offers several advantages:

  • Accessibility: Web applications are accessible from any device with a web browser, eliminating the need for specific software installations.
  • Customization: You have complete control over the design and functionality, allowing you to tailor the calculator to your specific needs.
  • Learning Opportunity: Building a calculator is a fantastic way to learn fundamental web development concepts, including state management, event handling, and user interface design.
  • Portfolio Piece: A functional calculator is a great addition to your portfolio, showcasing your ability to build interactive web applications.

Prerequisites

Before we begin, ensure you have the following:

  • Node.js and npm (or yarn) installed: These are essential for managing project dependencies and running the development server.
  • A basic understanding of HTML, CSS, and JavaScript: Familiarity with these languages will greatly assist in understanding the code.
  • A code editor: Choose your favorite code editor (e.g., VS Code, Sublime Text, Atom).

Step-by-Step Guide

1. Setting Up the Next.js Project

First, let’s create a new Next.js project. Open your terminal and run the following command:

npx create-next-app calculator-app

This command will create a new directory named `calculator-app` with the basic structure of a Next.js project. Navigate into the project directory:

cd calculator-app

2. Project Structure Overview

Inside the `calculator-app` directory, you’ll find the following key files and directories:

  • pages/: This directory contains your application’s pages. Each file in this directory represents a route in your application. For example, pages/index.js will be the home page.
  • public/: This directory is for static assets like images, fonts, and other files.
  • styles/: This directory is where you’ll put your CSS styles.
  • package.json: This file lists your project’s dependencies and scripts.

3. Creating the Calculator UI (User Interface)

Now, let’s build the user interface for our calculator. Open `pages/index.js` and replace the existing code with the following:

import { useState } from 'react';

export default function Home() {
  const [result, setResult] = useState("0");

  return (
    <div className="calculator-container">
      <input type="text" className="calculator-screen" value={result} readOnly />
      <div className="calculator-buttons">
        <button className="btn operator" onClick={() => {}}>+</button>
        <button className="btn operator" onClick={() => {}}>-</button>
        <button className="btn operator" onClick={() => {}}>*</button>
        <button className="btn operator" onClick={() => {}}>/</button>
        <button className="btn number" onClick={() => {}}>7</button>
        <button className="btn number" onClick={() => {}}>8</button>
        <button className="btn number" onClick={() => {}}>9</button>
        <button className="btn number" onClick={() => {}}>4</button>
        <button className="btn number" onClick={() => {}}>5</button>
        <button className="btn number" onClick={() => {}}>6</button>
        <button className="btn number" onClick={() => {}}>1</button>
        <button className="btn number" onClick={() => {}}>2</button>
        <button className="btn number" onClick={() => {}}>3</button>
        <button className="btn number" onClick={() => {}}>0</button>
        <button className="btn decimal" onClick={() => {}}>.</button>
        <button className="btn all-clear" onClick={() => {}}>AC</button>
        <button className="btn equal-sign" onClick={() => {}}>=</button>
      </div>
    </div>
  );
}

Let’s break down this code:

  • We import the `useState` hook from React to manage the calculator’s display value.
  • We initialize a state variable `result` with a default value of “0”. This variable will hold the current value displayed on the calculator screen.
  • The JSX (JavaScript XML) code defines the structure of the calculator UI:
  • An `input` field with the class “calculator-screen” displays the current `result`. The `readOnly` attribute prevents the user from directly typing into the input.
  • A `div` with the class “calculator-buttons” contains the buttons for numbers, operators, and other functions.
  • Each button has a class (e.g., “btn number”, “btn operator”, “btn equal-sign”) for styling and an `onClick` event handler (currently empty) that will trigger a function when the button is clicked.

4. Adding Basic Styling with CSS

To make the calculator visually appealing, we’ll add some CSS styles. Create a file named `styles/Calculator.module.css` (or you can use the global styles in `styles/globals.css`, but creating a module is a good practice for component-specific styles).


.calculator-container {
  width: 300px;
  margin: 50px auto;
  border: 1px solid #ccc;
  border-radius: 5px;
  overflow: hidden;
}

.calculator-screen {
  width: 100%;
  padding: 15px;
  font-size: 2em;
  text-align: right;
  border: none;
  background-color: #f0f0f0;
  box-sizing: border-box;
}

.calculator-buttons {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

.btn {
  padding: 15px;
  font-size: 1.5em;
  text-align: center;
  border: 1px solid #ccc;
  background-color: #fff;
  cursor: pointer;
}

.btn:hover {
  background-color: #eee;
}

.operator, .equal-sign {
  background-color: #f0f0f0;
}

.all-clear {
  background-color: #ff5722;
  color: white;
}

Now, import this CSS module into your `pages/index.js` file and apply the styles:

import { useState } from 'react';
import styles from '../styles/Calculator.module.css';

export default function Home() {
  const [result, setResult] = useState("0");

  return (
    <div className={styles["calculator-container"]}>
      <input type="text" className={styles["calculator-screen"]} value={result} readOnly />
      <div className={styles["calculator-buttons"]}>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => {}}>+</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => {}}>-</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => {}}>*</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => {}}>/</button>
        <button className={styles["btn number"]} onClick={() => {}}>7</button>
        <button className={styles["btn number"]} onClick={() => {}}>8</button>
        <button className={styles["btn number"]} onClick={() => {}}>9</button>
        <button className={styles["btn number"]} onClick={() => {}}>4</button>
        <button className={styles["btn number"]} onClick={() => {}}>5</button>
        <button className={styles["btn number"]} onClick={() => {}}>6</button>
        <button className={styles["btn number"]} onClick={() => {}}>1</button>
        <button className={styles["btn number"]} onClick={() => {}}>2</button>
        <button className={styles["btn number"]} onClick={() => {}}>3</button>
        <button className={styles["btn number"]} onClick={() => {}}>0</button>
        <button className={styles["btn decimal"]} onClick={() => {}}>.</button>
        <button className={styles["btn all-clear"]} onClick={() => {}}>AC</button>
        <button className={styles["btn equal-sign"]} onClick={() => {}}>=</button>
      </div>
    </div>
  );
}

In this code:

  • We import the CSS module using `import styles from ‘../styles/Calculator.module.css’;`.
  • We apply the CSS classes using `styles[“className”]`. Note the use of bracket notation, which is required when using CSS modules.
  • We use template literals to combine multiple classes (e.g., `className={`${styles[“btn”]} ${styles[“operator”]}`})

5. Implementing Button Functionality

Now, let’s add the logic to handle button clicks. We’ll start by adding the functionality to display numbers and handle the AC (All Clear) button.

Modify the `Home` function in `pages/index.js` as follows:

import { useState } from 'react';
import styles from '../styles/Calculator.module.css';

export default function Home() {
  const [result, setResult] = useState("0");

  const handleNumberClick = (number) => {
    if (result === "0") {
      setResult(number.toString());
    } else {
      setResult(result + number.toString());
    }
  };

  const handleClearClick = () => {
    setResult("0");
  };

  return (
    <div className={styles["calculator-container"]}>
      <input type="text" className={styles["calculator-screen"]} value={result} readOnly />
      <div className={styles["calculator-buttons"]}>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => {}}>+</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => {}}>-</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => {}}>*</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => {}}>/</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(7)}>7</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(8)}>8</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(9)}>9</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(4)}>4</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(5)}>5</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(6)}>6</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(1)}>1</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(2)}>2</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(3)}>3</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(0)}>0</button>
        <button className={styles["btn decimal"]} onClick={() => {}}>.</button>
        <button className={styles["btn all-clear"]} onClick={handleClearClick}>AC</button>
        <button className={styles["btn equal-sign"]} onClick={() => {}}>=</button>
      </div>
    </div>
  );
}

Here’s what changed:

  • We added two functions:
  • handleNumberClick(number): This function is called when a number button is clicked. It updates the `result` state. If the current `result` is “0”, it replaces it with the clicked number. Otherwise, it appends the clicked number to the current `result`.
  • handleClearClick(): This function is called when the AC button is clicked. It resets the `result` state to “0”.
  • We updated the `onClick` event handlers of the number buttons to call `handleNumberClick()` with the corresponding number.
  • We updated the `onClick` event handler of the AC button to call `handleClearClick()`.

6. Implementing Operator and Equals Functionality

Now, let’s add the functionality for operators (+, -, *, /) and the equals (=) button.

Modify the `Home` function in `pages/index.js` as follows:

import { useState } from 'react';
import styles from '../styles/Calculator.module.css';

export default function Home() {
  const [result, setResult] = useState("0");
  const [expression, setExpression] = useState(""); // Store the full expression

  const handleNumberClick = (number) => {
    if (result === "0") {
      setResult(number.toString());
    } else {
      setResult(result + number.toString());
    }
  };

  const handleOperatorClick = (operator) => {
    setExpression(result + operator); // Store the current result and the operator
    setResult("0"); // Reset result for the next number input
  };

  const handleEqualsClick = () => {
    try {
      const calculationResult = eval(expression + result); // Evaluate the expression
      setResult(calculationResult.toString());
      setExpression(""); // Clear the expression after calculation
    } catch (error) {
      setResult("Error"); // Handle errors (e.g., division by zero)
      setExpression("");
    }
  };

  const handleClearClick = () => {
    setResult("0");
    setExpression(""); // Clear the expression as well
  };

  return (
    <div className={styles["calculator-container"]}>
      <input type="text" className={styles["calculator-screen"]} value={result} readOnly />
      <div className={styles["calculator-buttons"]}>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => handleOperatorClick('+')}>+</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => handleOperatorClick('-')}>-</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => handleOperatorClick('*')}>*</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => handleOperatorClick('/')}>/</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(7)}>7</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(8)}>8</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(9)}>9</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(4)}>4</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(5)}>5</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(6)}>6</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(1)}>1</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(2)}>2</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(3)}>3</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(0)}>0</button>
        <button className={styles["btn decimal"]} onClick={() => {}}>.</button>
        <button className={styles["btn all-clear"]} onClick={handleClearClick}>AC</button>
        <button className={styles["btn equal-sign"]} onClick={handleEqualsClick}>=</button>
      </div>
    </div>
  );
}

Here’s what changed:

  • We added a new state variable: expression. This will store the mathematical expression as the user inputs it.
  • We added the handleOperatorClick(operator) function. This function stores the current `result` and the selected `operator` in the `expression` state and resets the `result` to “0” to prepare for the next number input.
  • We added the handleEqualsClick() function. This function evaluates the expression stored in `expression` (plus the current `result`) using the JavaScript `eval()` function. It then updates the `result` with the calculated value. It also includes a `try…catch` block to handle potential errors, such as division by zero. The expression is cleared after the calculation.
  • We updated the `onClick` event handlers of the operator buttons to call `handleOperatorClick()` with the corresponding operator.
  • We updated the `onClick` event handler of the equals button to call `handleEqualsClick()`.
  • The `handleClearClick` function now also clears the `expression`.

7. Implementing Decimal and Error Handling (Optional)

Let’s add a decimal button and improve error handling.

Modify the `Home` function in `pages/index.js` as follows:

import { useState } from 'react';
import styles from '../styles/Calculator.module.css';

export default function Home() {
  const [result, setResult] = useState("0");
  const [expression, setExpression] = useState("");

  const handleNumberClick = (number) => {
    if (result === "0" || result === "Error") {
      setResult(number.toString());
    } else {
      setResult(result + number.toString());
    }
  };

  const handleDecimalClick = () => {
    if (!result.includes(".")) {
      setResult(result + ".");
    }
  };

  const handleOperatorClick = (operator) => {
    setExpression(expression + result + operator); // Store the current result and the operator
    setResult("0"); // Reset result for the next number input
  };

  const handleEqualsClick = () => {
    try {
      const calculationResult = eval(expression + result); // Evaluate the expression
      setResult(calculationResult.toString());
      setExpression(""); // Clear the expression after calculation
    } catch (error) {
      setResult("Error"); // Handle errors (e.g., division by zero)
      setExpression("");
    }
  };

  const handleClearClick = () => {
    setResult("0");
    setExpression(""); // Clear the expression as well
  };

  return (
    <div className={styles["calculator-container"]}>
      <input type="text" className={styles["calculator-screen"]} value={result} readOnly />
      <div className={styles["calculator-buttons"]}>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => handleOperatorClick('+')}>+</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => handleOperatorClick('-')}>-</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => handleOperatorClick('*')}>*</button>
        <button className={`${styles["btn"]} ${styles["operator"]}`} onClick={() => handleOperatorClick('/')}>/</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(7)}>7</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(8)}>8</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(9)}>9</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(4)}>4</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(5)}>5</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(6)}>6</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(1)}>1</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(2)}>2</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(3)}>3</button>
        <button className={styles["btn number"]} onClick={() => handleNumberClick(0)}>0</button>
        <button className={styles["btn decimal"]} onClick={handleDecimalClick}>.</button>
        <button className={styles["btn all-clear"]} onClick={handleClearClick}>AC</button>
        <button className={styles["btn equal-sign"]} onClick={handleEqualsClick}>=</button>
      </div>
    </div>
  );
}

Here’s what changed:

  • We added the handleDecimalClick() function. This function adds a decimal point to the `result` if it doesn’t already contain one.
  • We updated `handleNumberClick()` to reset the result if the current result is “0” or “Error”.
  • We updated `handleOperatorClick()` to store the current expression and operator.
  • We added the `onClick` event handler for the decimal button.

8. Running the Calculator

To run your calculator, execute the following command in your terminal:

npm run dev

This will start the Next.js development server. Open your web browser and go to `http://localhost:3000` to see your calculator in action. You should now be able to perform basic calculations.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to avoid or fix them:

  • Incorrect CSS Module Import: Make sure you import your CSS module correctly using `import styles from ‘../styles/Calculator.module.css’;`. Also, remember to use bracket notation (e.g., `styles[“calculator-container”]`) when applying the styles.
  • State Not Updating: Ensure that you are correctly using the `setResult()` function to update the state. Incorrectly updating the state can lead to the UI not reflecting the changes.
  • Operator Precedence Issues: The `eval()` function doesn’t inherently handle operator precedence (order of operations) correctly. For more complex calculators, you’ll need to implement a parsing and evaluation strategy that respects operator precedence (e.g., using a library like `mathjs` or implementing your own parsing logic).
  • Error Handling: Remember to include error handling (e.g., using `try…catch` blocks) to gracefully handle potential issues like division by zero.
  • Using `eval()` without caution: While convenient for this simple example, `eval()` can be a security risk if you’re taking user input for calculations. For production environments with user-supplied input, consider using a safer alternative, such as a dedicated math parsing library.

Key Takeaways

  • You’ve learned how to create a basic calculator UI using React and Next.js.
  • You’ve gained experience with state management using `useState`.
  • You’ve learned how to handle button clicks and update the UI accordingly.
  • You’ve learned how to use CSS Modules for styling.
  • You’ve gained an understanding of the basic structure of a Next.js project.

FAQ

Here are some frequently asked questions:

  1. Can I deploy this calculator? Yes, you can deploy your Next.js application to various platforms like Vercel, Netlify, or AWS.
  2. How can I add more advanced features? You can add features like memory functions (M+, M-, MR), scientific functions (sin, cos, tan), and more by extending the existing code and adding new button functionalities and mathematical operations. Consider using JavaScript’s built-in `Math` object for these operations.
  3. Why use Next.js for a simple calculator? Next.js offers features like server-side rendering (SSR) and static site generation (SSG), which can improve performance and SEO. Although a simple calculator doesn’t necessarily benefit greatly from these features, building it in Next.js allows you to learn the framework and prepare for more complex projects.
  4. How can I improve the user interface? You can customize the styling further, add animations, and improve the layout for a more user-friendly experience. Consider using a UI component library like Material UI or Chakra UI to speed up the development process.
  5. What are the alternatives to `eval()`? For more secure and robust calculation logic, you can use a math parsing library like `mathjs` or implement your own parsing and evaluation logic. These alternatives provide better control over the calculations and prevent potential security vulnerabilities.

Congratulations! You’ve successfully built a basic web-based calculator using Next.js. This project is a stepping stone to more complex web development projects. Practice and experimentation are key to mastering the concepts. Continue exploring the capabilities of Next.js and React to build more sophisticated and interactive web applications. You can extend this project by adding more features or by improving the UI/UX. The possibilities are endless. Keep coding, keep learning, and enjoy the journey of web development.