Build a Simple Next.js Interactive Web-Based Personal Finance Tracker

Written by

in

Managing personal finances can often feel like navigating a complex maze. Keeping track of income, expenses, and savings manually can be time-consuming and prone to errors. Wouldn’t it be great to have a simple, interactive tool that helps you visualize your financial health at a glance? This tutorial will guide you through building a personal finance tracker using Next.js, empowering you to take control of your money with a user-friendly web application.

Why Build a Personal Finance Tracker?

A personal finance tracker offers several benefits:

  • Improved Financial Awareness: Gain insights into your spending habits and identify areas where you can save.
  • Budgeting Made Easy: Create and manage budgets effectively, helping you stay on track with your financial goals.
  • Goal Setting and Tracking: Set financial goals, such as saving for a down payment or paying off debt, and track your progress.
  • Reduced Financial Stress: Having a clear understanding of your finances can reduce anxiety and stress related to money.

This project is perfect for beginners and intermediate developers who want to learn Next.js while creating something practical and useful. We’ll cover essential concepts like state management, data visualization, and form handling, all within a clean and modern web application framework.

Project Overview

Our personal finance tracker will allow users to:

  • Add income and expenses.
  • Categorize transactions.
  • View a summary of their finances.
  • See income and expense trends.

Prerequisites

Before you begin, make sure you have the following installed:

  • Node.js and npm: Used for managing project dependencies and running the development server.
  • A code editor: Such as VS Code, Sublime Text, or Atom.
  • Basic knowledge of JavaScript and React: Familiarity with these technologies will be helpful, but we’ll provide explanations along the way.

Step-by-Step Guide

1. 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 personal-finance-tracker

This command creates a new Next.js project named “personal-finance-tracker”. Navigate into your project directory:

cd personal-finance-tracker

Now, start the development server:

npm run dev

Open your browser and go to http://localhost:3000 to see the default Next.js welcome page. You’re now ready to start building your application!

2. Project Structure and Initial Setup

Let’s organize our project. We’ll create the following structure:

personal-finance-tracker/
├── pages/
│   ├── index.js          // Main page
│   └── _app.js           // Global styles and layout
├── components/
│   ├── AddTransactionForm.js // Form to add transactions
│   ├── TransactionList.js     // Display transaction list
│   ├── Summary.js           // Display financial summary
│   └── Chart.js             // Display income/expense chart
├── styles/
│   └── global.css          // Global styles
└── public/
    └── ...

Create the `components` and `styles` folders in your project’s root directory. We’ll start by modifying `pages/index.js` to serve as our main page and layout.

Modify `pages/index.js`:

import AddTransactionForm from '../components/AddTransactionForm';
import TransactionList from '../components/TransactionList';
import Summary from '../components/Summary';
import Chart from '../components/Chart';

function HomePage() {
  return (
    <div className="container">
      <h1>Personal Finance Tracker</h1>
      <AddTransactionForm />
      <Summary />
      <Chart />
      <TransactionList />
    </div>
  );
}

export default HomePage;

Modify `pages/_app.js` to include global styles:

import '../styles/global.css';

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp;

Create `styles/global.css` with some basic styling:

.container {
  max-width: 800px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 5px;
  font-family: sans-serif;
}

h1 {
  text-align: center;
}

3. Building the Add Transaction Form

Let’s create the form to add new transactions. This component will handle user input for income and expenses.

Create `components/AddTransactionForm.js`:

import { useState } from 'react';

function AddTransactionForm() {
  const [description, setDescription] = useState('');
  const [amount, setAmount] = useState('');
  const [type, setType] = useState('income'); // or 'expense'

  const handleSubmit = (e) => {
    e.preventDefault();
    // TODO: Implement transaction adding logic here
    console.log('Adding transaction:', { description, amount, type });
    // Clear the form
    setDescription('');
    setAmount('');
    setType('income');
  };

  return (
    <form onSubmit={handleSubmit} className="add-transaction-form">
      <h2>Add Transaction</h2>
      <div>
        <label htmlFor="description">Description:</label>
        <input
          type="text"
          id="description"
          value={description}
          onChange={(e) => setDescription(e.target.value)}
          required
        />
      </div>
      <div>
        <label htmlFor="amount">Amount:</label>
        <input
          type="number"
          id="amount"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
          required
        />
      </div>
      <div>
        <label htmlFor="type">Type:</label>
        <select id="type" value={type} onChange={(e) => setType(e.target.value)}>
          <option value="income">Income</option>
          <option value="expense">Expense</option>
        </select>
      </div>
      <button type="submit">Add Transaction</button>
    </form>
  );
}

export default AddTransactionForm;

Let’s add some basic styling for the form in `styles/global.css`:

.add-transaction-form {
  margin-bottom: 20px;
  padding: 10px;
  border: 1px solid #eee;
  border-radius: 5px;
}

.add-transaction-form div {
  margin-bottom: 10px;
}

.add-transaction-form label {
  display: block;
  margin-bottom: 5px;
}

.add-transaction-form input, .add-transaction-form select {
  width: 100%;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.add-transaction-form button {
  background-color: #4CAF50;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

4. Managing Transaction State

We’ll use React’s `useState` hook to manage the transactions. We’ll store an array of transaction objects. Let’s create a global state using context API for this (or you can use Redux or Zustand for more complex apps).

Create a context file `context/TransactionContext.js`:

import { createContext, useState, useContext } from 'react';

const TransactionContext = createContext();

export const useTransactions = () => {
  return useContext(TransactionContext);
};

export const TransactionProvider = ({ children }) => {
  const [transactions, setTransactions] = useState([]);

  const addTransaction = (transaction) => {
    setTransactions([...transactions, transaction]);
  };

  return (
    <TransactionContext.Provider value={{ transactions, addTransaction }}>
      {children}
    </TransactionContext.Provider>
  );
};

Now, wrap your application with the `TransactionProvider` in `_app.js`:

import '../styles/global.css';
import { TransactionProvider } from '../context/TransactionContext';

function MyApp({ Component, pageProps }) {
  return (
    <TransactionProvider>
      <Component {...pageProps} />
    </TransactionProvider>
  );
}

export default MyApp;

Now, let’s use the transaction context in `AddTransactionForm.js` to add transactions:

import { useState } from 'react';
import { useTransactions } from '../context/TransactionContext';

function AddTransactionForm() {
  const [description, setDescription] = useState('');
  const [amount, setAmount] = useState('');
  const [type, setType] = useState('income'); // or 'expense'
  const { addTransaction } = useTransactions();

  const handleSubmit = (e) => {
    e.preventDefault();
    const newTransaction = {
      id: Date.now(), // Generate a simple unique ID
      description,
      amount: parseFloat(amount),
      type,
    };
    addTransaction(newTransaction);

    // Clear the form
    setDescription('');
    setAmount('');
    setType('income');
  };

  return (
    <form onSubmit={handleSubmit} className="add-transaction-form">
      <h2>Add Transaction</h2>
      <div>
        <label htmlFor="description">Description:</label>
        <input
          type="text"
          id="description"
          value={description}
          onChange={(e) => setDescription(e.target.value)}
          required
        />
      </div>
      <div>
        <label htmlFor="amount">Amount:</label>
        <input
          type="number"
          id="amount"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
          required
        />
      </div>
      <div>
        <label htmlFor="type">Type:</label>
        <select id="type" value={type} onChange={(e) => setType(e.target.value)}>
          <option value="income">Income</option>
          <option value="expense">Expense</option>
        </select>
      </div>
      <button type="submit">Add Transaction</button>
    </form>
  );
}

export default AddTransactionForm;

5. Displaying the Transaction List

Now, let’s display the list of transactions.

Create `components/TransactionList.js`:

import { useTransactions } from '../context/TransactionContext';

function TransactionList() {
  const { transactions } = useTransactions();

  return (
    <div className="transaction-list">
      <h2>Transactions</h2>
      <ul>
        {transactions.map((transaction) => (
          <li key={transaction.id} className={transaction.type === 'expense' ? 'expense' : 'income'}>
            <span>{transaction.description}</span> - <span>${transaction.amount}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TransactionList;

Add some basic styling to `styles/global.css`:

.transaction-list {
  margin-top: 20px;
  padding: 10px;
  border: 1px solid #eee;
  border-radius: 5px;
}

.transaction-list ul {
  list-style: none;
  padding: 0;
}

.transaction-list li {
  padding: 10px;
  border-bottom: 1px solid #eee;
  display: flex;
  justify-content: space-between;
}

.transaction-list li:last-child {
  border-bottom: none;
}

.expense {
  color: red;
}

.income {
  color: green;
}

6. Displaying the Summary

Let’s create a component to display the financial summary (income, expenses, and balance).

Create `components/Summary.js`:

import { useTransactions } from '../context/TransactionContext';

function Summary() {
  const { transactions } = useTransactions();

  const income = transactions
    .filter((transaction) => transaction.type === 'income')
    .reduce((sum, transaction) => sum + transaction.amount, 0);

  const expenses = transactions
    .filter((transaction) => transaction.type === 'expense')
    .reduce((sum, transaction) => sum + transaction.amount, 0);

  const balance = income - expenses;

  return (
    <div className="summary">
      <h2>Summary</h2>
      <div>Income: ${income.toFixed(2)}</div>
      <div>Expenses: ${expenses.toFixed(2)}</div>
      <div>Balance: ${balance.toFixed(2)}</div>
    </div>
  );
}

export default Summary;

Add some styling to `styles/global.css`:

.summary {
  margin-top: 20px;
  padding: 10px;
  border: 1px solid #eee;
  border-radius: 5px;
}

.summary div {
  margin-bottom: 5px;
}

7. Adding a Chart

Let’s use a simple chart to visualize income and expenses. We’ll use a library called `react-chartjs-2` for this. Install it by running:

npm install react-chartjs-2 chart.js

Create `components/Chart.js`:

import { Bar } from 'react-chartjs-2';
import { useTransactions } from '../context/TransactionContext';
import { Chart as ChartJS } from 'chart.js/auto';

function Chart() {
  const { transactions } = useTransactions();

  const income = transactions
    .filter((transaction) => transaction.type === 'income')
    .reduce((sum, transaction) => sum + transaction.amount, 0);

  const expenses = transactions
    .filter((transaction) => transaction.type === 'expense')
    .reduce((sum, transaction) => sum + transaction.amount, 0);

  const chartData = {
    labels: ['Income', 'Expenses'],
    datasets: [
      {
        label: 'Amount',
        data: [income, expenses],
        backgroundColor: ['rgba(75,192,192,0.6)', 'rgba(255,99,132,0.6)'],
        borderColor: ['rgba(75,192,192,1)', 'rgba(255,99,132,1)'],
        borderWidth: 1,
      },
    ],
  };

  const chartOptions = {
    scales: {
      y: {
        beginAtZero: true,
      },
    },
  };

  return (
    <div className="chart">
      <h2>Income vs Expenses</h2>
      <Bar data={chartData} options={chartOptions} />
    </div>
  );
}

export default Chart;

Add styling to `styles/global.css`:

.chart {
  margin-top: 20px;
  padding: 10px;
  border: 1px solid #eee;
  border-radius: 5px;
}

8. Handling Common Mistakes and Improvements

Common Mistakes:

  • Incorrect Data Types: Make sure to parse the amount input to a number using `parseFloat()` to avoid string concatenation issues.
  • Missing Dependencies: Double-check that you’ve installed all the necessary packages (like `react-chartjs-2` and `chart.js`).
  • State Management Errors: Ensure that you are correctly updating the state of your transactions. If using context, make sure the context provider is correctly wrapped around the app.
  • UI Issues: If the UI doesn’t look right, review your CSS and ensure that styles are applied correctly. Use your browser’s developer tools to inspect elements and identify any styling conflicts.

Improvements:

  • Categories: Add categories to transactions (e.g., “Groceries,” “Rent,” “Salary”) for more detailed analysis.
  • Date Picker: Include a date picker to track transactions over time.
  • Data Persistence: Store the transaction data in local storage or a database to persist the data between sessions.
  • More Advanced Charts: Use different chart types (pie charts, line charts) to visualize data in more ways.
  • User Authentication: Implement user authentication to secure the application and allow for multiple users.

Key Takeaways

  • You’ve learned how to create a basic personal finance tracker using Next.js.
  • You’ve mastered state management using React’s `useState` hook and context API.
  • You’ve integrated a chart library to visualize financial data.
  • You’ve built a user-friendly interface for adding and viewing transactions.

FAQ

  1. Can I use a different state management library? Yes, you can use Redux, Zustand, or any other state management library that you prefer. The core logic of adding, displaying, and summarizing transactions will remain the same.
  2. How can I add data persistence? You can use local storage to save data in the browser. For more robust storage, consider using a database like Firebase, MongoDB, or PostgreSQL. You’ll need to install the necessary packages for interacting with your chosen database and write code to save and retrieve data.
  3. How do I deploy this application? You can deploy your Next.js application to platforms like Vercel, Netlify, or AWS. Vercel is the easiest option for Next.js applications, as it is built by the same company. You’ll need to create an account on your chosen platform, connect your GitHub repository, and follow the deployment instructions.
  4. Can I add more features? Absolutely! This is a starting point. Feel free to add features like recurring transactions, budgeting tools, and more sophisticated reporting.

Congratulations! You’ve successfully built a simple personal finance tracker with Next.js. This project provides a solid foundation for understanding the core concepts of Next.js and React, including state management, component structure, and data visualization. By expanding on this base, you can create a powerful tool to manage your finances effectively. The ability to track income, expenses, and visualize your financial health in an easy-to-use application is a valuable skill in today’s world. As you continue to develop and refine this application, you’ll not only learn more about Next.js but also gain a deeper understanding of your own financial habits, leading to better financial decisions and a more secure financial future. Remember to experiment with different features, explore advanced styling techniques, and most importantly, have fun while you’re learning!