Build a Simple Next.js Interactive Tip Calculator

Written by

in

In the digital age, calculating tips has become a ubiquitous task, from splitting bills at a restaurant to figuring out service fees. While many apps and websites offer tip calculators, building your own provides a fantastic opportunity to learn and practice web development fundamentals, particularly with a modern framework like Next.js. This tutorial will guide you through creating a simple, interactive tip calculator, perfect for beginners to intermediate developers looking to hone their skills and understand the power of Next.js.

Why Build a Tip Calculator?

Creating a tip calculator might seem like a small project, but it encompasses several essential web development concepts. You’ll learn how to handle user input, perform calculations, update the user interface dynamically, and manage state – all critical skills for building more complex applications. Moreover, this project is a great way to familiarize yourself with Next.js’s features, such as server-side rendering, routing, and component-based architecture.

Prerequisites

Before we dive in, ensure you have the following:

  • A basic understanding of HTML, CSS, and JavaScript.
  • Node.js and npm (or yarn) installed on your system.
  • A code editor (like VS Code, Sublime Text, or Atom).

Setting Up Your Next.js Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app@latest tip-calculator

This command will set up a new Next.js project named “tip-calculator.” You’ll be prompted to answer a few questions about your project. You can typically accept the defaults. Once the project is created, navigate into the project directory:

cd tip-calculator

Now, start the development server:

npm run dev

Your Next.js application should now be running on http://localhost:3000. You should see the default Next.js welcome page.

Building the Tip Calculator UI

We’ll now create the user interface for our tip calculator. This will involve creating input fields for the bill amount, the tip percentage, and the number of people splitting the bill, as well as displaying the calculated tip and total amount.

Modifying the Default Page

Open the `pages/index.js` file in your project. This file represents the home page of your application. Replace the existing code with the following:

import { useState } from 'react';

export default function Home() {
  const [billAmount, setBillAmount] = useState('');
  const [tipPercentage, setTipPercentage] = useState(15);
  const [numberOfPeople, setNumberOfPeople] = useState(1);
  const [tipAmount, setTipAmount] = useState(0);
  const [totalAmount, setTotalAmount] = useState(0);

  const calculateTip = () => {
    const bill = parseFloat(billAmount);
    const tip = parseFloat(tipPercentage) / 100;
    const people = parseInt(numberOfPeople);

    if (isNaN(bill) || bill <= 0) {
      setTipAmount(0);
      setTotalAmount(0);
      return;
    }

    const tipValue = bill * tip;
    const total = bill + tipValue;
    const tipPerPerson = tipValue / people;
    const totalPerPerson = total / people;

    setTipAmount(tipPerPerson);
    setTotalAmount(totalPerPerson);
  };

  return (
    <div className="container">
      <h1>Tip Calculator</h1>

      <div className="input-group">
        <label htmlFor="billAmount">Bill Amount:</label>
        <input
          type="number"
          id="billAmount"
          value={billAmount}
          onChange={(e) => setBillAmount(e.target.value)}
        /
        >
      </div>

      <div className="input-group">
        <label htmlFor="tipPercentage">Tip Percentage:</label>
        <input
          type="number"
          id="tipPercentage"
          value={tipPercentage}
          onChange={(e) => setTipPercentage(e.target.value)}
        /
        >
      </div>

      <div className="input-group">
        <label htmlFor="numberOfPeople">Number of People:</label>
        <input
          type="number"
          id="numberOfPeople"
          value={numberOfPeople}
          onChange={(e) => setNumberOfPeople(e.target.value)}
        /
        >
      </div>

      <button onClick={calculateTip}>Calculate Tip</button>

      <div className="results">
        <p>Tip Amount per Person: ${tipAmount.toFixed(2)}</p>
        <p>Total Amount per Person: ${totalAmount.toFixed(2)}</p>
      </div>
    </div>
  );
}

Let’s break down this code:

  • We import the `useState` hook from React to manage the state of our input fields and calculated values.
  • We declare state variables: `billAmount`, `tipPercentage`, `numberOfPeople`, `tipAmount`, and `totalAmount`.
  • We have input fields for the bill amount, tip percentage, and number of people. The `onChange` event handlers update the state variables as the user types.
  • A `calculateTip` function performs the calculations and updates the `tipAmount` and `totalAmount` state variables.
  • We display the calculated tip and total amount in the results section.

Adding Basic Styling with CSS Modules

To style our calculator, let’s create a CSS Modules file. In the `styles` directory, create a new file named `Home.module.css` and add the following styles:


.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  font-family: sans-serif;
}

.input-group {
  margin-bottom: 10px;
  display: flex;
  flex-direction: column;
  width: 200px;
}

.input-group label {
  margin-bottom: 5px;
  font-weight: bold;
}

.input-group input {
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 16px;
}

button {
  padding: 10px 20px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
  margin-top: 10px;
}

button:hover {
  background-color: #3e8e41;
}

.results {
  margin-top: 20px;
  border: 1px solid #ccc;
  padding: 10px;
  border-radius: 4px;
  text-align: center;
}

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


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

export default function Home() {
  // ... (rest of the component code)
  return (
    <div className={styles.container}>
      <h1>Tip Calculator</h1>

      <div className={styles.inputGroup}>
        <label htmlFor="billAmount">Bill Amount:</label>
        <input
          type="number"
          id="billAmount"
          value={billAmount}
          onChange={(e) => setBillAmount(e.target.value)}
        />
      </div>

      <div className={styles.inputGroup}>
        <label htmlFor="tipPercentage">Tip Percentage:</label>
        <input
          type="number"
          id="tipPercentage"
          value={tipPercentage}
          onChange={(e) => setTipPercentage(e.target.value)}
        />
      </div>

      <div className={styles.inputGroup}>
        <label htmlFor="numberOfPeople">Number of People:</label>
        <input
          type="number"
          id="numberOfPeople"
          value={numberOfPeople}
          onChange={(e) => setNumberOfPeople(e.target.value)}
        />
      </div>

      <button onClick={calculateTip}>Calculate Tip</button>

      <div className={styles.results}>
        <p>Tip Amount per Person: ${tipAmount.toFixed(2)}</p>
        <p>Total Amount per Person: ${totalAmount.toFixed(2)}</p>
      </div>
    </div>
  );
}

Now, when you refresh your page, the calculator should be styled. CSS Modules help prevent style conflicts by scoping the CSS to the component.

Implementing the Calculation Logic

The core of our application is the `calculateTip` function. Let’s walk through it:


  const calculateTip = () => {
    const bill = parseFloat(billAmount);
    const tip = parseFloat(tipPercentage) / 100;
    const people = parseInt(numberOfPeople);

    if (isNaN(bill) || bill <= 0) {
      setTipAmount(0);
      setTotalAmount(0);
      return;
    }

    const tipValue = bill * tip;
    const total = bill + tipValue;
    const tipPerPerson = tipValue / people;
    const totalPerPerson = total / people;

    setTipAmount(tipPerPerson);
    setTotalAmount(totalPerPerson);
  };

Here’s a breakdown:

  • We parse the input values from the input fields using `parseFloat()` and `parseInt()`.
  • We check if the bill amount is a valid number and greater than zero. If not, we set the tip and total amounts to zero and return. This prevents errors when invalid input is entered.
  • We calculate the tip amount, the total amount, the tip per person, and the total amount per person.
  • Finally, we update the `tipAmount` and `totalAmount` state variables to reflect the calculated values, which triggers a re-render of the component.

Handling User Input and State Management

React’s `useState` hook is crucial for managing the state of our calculator. Each input field’s `onChange` event updates the corresponding state variable. This is a fundamental concept in React: when the state changes, the component re-renders, displaying the updated values.

Here’s how the state updates work:

  • When the user types in the “Bill Amount” field, the `setBillAmount` function updates the `billAmount` state.
  • When the user types in the “Tip Percentage” field, the `setTipPercentage` function updates the `tipPercentage` state.
  • When the user types in the “Number of People” field, the `setNumberOfPeople` function updates the `numberOfPeople` state.
  • When the user clicks the “Calculate Tip” button, the `calculateTip` function is called, which calculates the results and updates the `tipAmount` and `totalAmount` states.
  • The component re-renders, displaying the calculated tip and total amounts.

Common Mistakes and How to Fix Them

As you build this calculator, you might encounter some common issues. Here are some of them and how to resolve them:

1. Incorrect Data Types

Problem: The input values from the input fields are strings by default. If you try to perform calculations without converting them to numbers, you’ll likely get unexpected results (e.g., string concatenation instead of addition). For example, if the bill is “10” and the tip is “10”, the result will be “1010” instead of “20”.

Solution: Use `parseFloat()` and `parseInt()` to convert the input values to numbers before performing calculations. Make sure to handle potential `NaN` (Not a Number) values.

2. Missing or Incorrect State Updates

Problem: The calculated results aren’t updating in the UI, or they’re not updating correctly. This usually means the state variables aren’t being updated properly.

Solution: Double-check that your `onChange` event handlers are correctly calling the setter functions (e.g., `setBillAmount`) and that the `calculateTip` function is updating the `tipAmount` and `totalAmount` state variables. Also, ensure that your component re-renders after the state is updated.

3. Styling Issues

Problem: The calculator looks unstyled or the styles aren’t being applied correctly.

Solution: Make sure you’ve imported your CSS module correctly (e.g., `import styles from ‘../styles/Home.module.css’;`) and that you’re applying the styles to your HTML elements using the correct class names (e.g., `className={styles.container}`). Also, check for any CSS conflicts or errors in your browser’s developer console.

4. Division by Zero

Problem: If the user enters “0” for the number of people, you’ll encounter a division by zero error, which will crash the app.

Solution: Add a check in your `calculateTip` function to prevent division by zero. You can either disable the calculation or display an error message if the number of people is zero.

Enhancements and Next Steps

Here are some ways you can enhance your tip calculator:

  • Add Tip Suggestions: Provide common tip percentage options (e.g., 10%, 15%, 20%) as buttons to make it easier for the user to select a tip percentage.
  • Implement a Reset Button: Add a button to reset all input fields to their default values.
  • Improve Error Handling: Display more user-friendly error messages if the input is invalid (e.g., “Please enter a valid bill amount”).
  • Add Dark Mode: Implement a dark mode toggle for a better user experience.
  • Use a Form: Wrap your input fields in a `<form>` element and add a submit handler to the form. This can improve accessibility and allow for more advanced input validation.
  • Add Accessibility Features: Ensure your calculator is accessible by using semantic HTML, providing labels for all input fields, and using ARIA attributes where needed.
  • Deploy Your App: Deploy your Next.js app to a platform like Vercel or Netlify to share it with the world.

Summary / Key Takeaways

Building a tip calculator in Next.js is a practical exercise that combines front-end development, state management, and basic calculations. By working through this project, you’ve gained hands-on experience with core React concepts like `useState`, event handling, and component rendering. You’ve also touched on styling with CSS Modules and the fundamentals of building a user interface. This project offers a solid foundation for understanding how Next.js works and provides a stepping stone to building more sophisticated web applications. The flexibility of Next.js, with its features like server-side rendering, allows for a more performant and SEO-friendly application, making it a great choice for this type of project and beyond. Now, with your own tip calculator, you have a tangible example of how to build interactive web applications and the skills to create something useful and engaging.

This project is more than just a tip calculator; it’s a foundation for your future Next.js endeavors. The skills you’ve learned here, from state management to UI design, are transferable to a wide range of web development tasks. Keep experimenting, keep learning, and don’t be afraid to take on more complex challenges. The web development world is constantly evolving, so continuous learning and practice are key to staying ahead. Embrace the learning process, and enjoy the journey of building your own web applications.