Managing finances can often feel like navigating a complex maze. Keeping track of income and expenses, categorizing transactions, and visualizing spending patterns are crucial steps towards financial health. In this tutorial, we’ll build a simple yet effective expense tracker using Next.js, a powerful React framework for building modern web applications. This project will not only introduce you to the fundamentals of Next.js but also provide practical experience in handling user input, managing state, and displaying data dynamically. By the end of this guide, you’ll have a functional expense tracker and a solid understanding of how to create interactive web applications.
Why Build an Expense Tracker?
An expense tracker is a valuable tool for anyone looking to gain control over their finances. It helps you understand where your money is going, identify areas where you can save, and make informed financial decisions. Beyond the personal benefits, building an expense tracker is a fantastic learning experience. It allows you to practice key programming concepts like:
- Handling user input: Collecting and validating data from users.
- Managing state: Storing and updating data within the application.
- Displaying data dynamically: Presenting information to the user in a clear and organized manner.
- Working with forms: Creating and managing form elements.
- Data manipulation: Performing calculations and organizing data.
This project is perfect for beginners and intermediate developers looking to expand their skillset and build something useful. Let’s dive in!
Prerequisites
Before we start, make sure you have the following installed on your system:
- Node.js and npm (or yarn): You’ll need Node.js and npm (Node Package Manager) or yarn to manage project dependencies. You can download them from the official Node.js website.
- A code editor: Choose your preferred code editor (e.g., VS Code, Sublime Text, Atom).
- Basic understanding of HTML, CSS, and JavaScript: Familiarity with these languages is essential for understanding the code.
Setting Up the 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 expense-tracker
This command will create a new Next.js project named “expense-tracker.” Navigate into the project directory:
cd expense-tracker
Now, start the development server:
npm run dev
Open your web browser and go to http://localhost:3000. You should see the default Next.js welcome page.
Project Structure Overview
Before we start coding, let’s briefly examine the basic structure of a Next.js project:
- 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 accessible at `/`.
- public/: This directory is for static assets like images, fonts, and other files you want to serve directly.
- styles/: This directory is for your CSS or styling files.
- package.json: This file lists your project dependencies and scripts.
Creating the Expense Tracker Components
We will create several components to build our expense tracker. These components will handle different aspects of the application, making the code more organized and maintainable.
1. ExpenseForm Component
This component will handle the form for adding new expenses. Create a new file named `components/ExpenseForm.js` and add the following code:
import { useState } from 'react';
const ExpenseForm = ({ onAddExpense }) => {
const [description, setDescription] = useState('');
const [amount, setAmount] = useState('');
const [category, setCategory] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!description || !amount || !category) {
alert('Please fill in all fields.');
return;
}
const newExpense = {
description,
amount: parseFloat(amount),
category,
date: new Date().toLocaleDateString(),
};
onAddExpense(newExpense);
setDescription('');
setAmount('');
setCategory('');
};
return (
<form onSubmit={handleSubmit} className="expense-form">
<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="category">Category:</label>
<select
id="category"
value={category}
onChange={(e) => setCategory(e.target.value)}
required
>
<option value="">Select Category</option>
<option value="food">Food</option>
<option value="transportation">Transportation</option>
<option value="housing">Housing</option>
<option value="utilities">Utilities</option>
<option value="entertainment">Entertainment</option>
</select>
</div>
<button type="submit">Add Expense</button>
<style jsx>{`
.expense-form {
display: flex;
flex-direction: column;
gap: 10px;
max-width: 300px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 5px;
}
.expense-form div {
display: flex;
flex-direction: column;
gap: 5px;
}
input, select {
padding: 8px;
border-radius: 4px;
border: 1px solid #ddd;
}
button {
padding: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #3e8e41;
}
`}</style>
</form>
);
};
export default ExpenseForm;
Explanation:
- We import the `useState` hook from React to manage the form input values.
- The component takes an `onAddExpense` prop, which is a function that will be called when a new expense is submitted.
- We use `useState` to define state variables for `description`, `amount`, and `category`.
- The `handleSubmit` function is called when the form is submitted. It prevents the default form submission behavior, validates the input, creates a new expense object, calls the `onAddExpense` function with the new expense, and resets the form fields.
- The component renders a form with input fields for description and amount, and a select element for category.
- Inline CSS is used for basic styling.
2. ExpenseList Component
This component will display the list of expenses. Create a new file named `components/ExpenseList.js` and add the following code:
const ExpenseList = ({ expenses }) => {
return (
<div className="expense-list">
<h2>Expenses</h2>
{expenses.length === 0 ? (
<p>No expenses to display.</p>
) : (
<ul>
{expenses.map((expense) => (
<li key={expense.description}
style={{
border: '1px solid #ccc',
padding: '10px',
marginBottom: '5px',
borderRadius: '4px',
}}
>
<div>Description: {expense.description}</div>
<div>Amount: ${expense.amount}</div>
<div>Category: {expense.category}</div>
<div>Date: {expense.date}</div>
</li>
))}
</ul>
)}
<style jsx>{`
.expense-list {
margin-top: 20px;
}
ul {
list-style: none;
padding: 0;
}
`}</style>
</div>
);
};
export default ExpenseList;
Explanation:
- The component takes an `expenses` prop, which is an array of expense objects.
- It renders a heading and a list of expenses.
- If there are no expenses, it displays a message.
- It iterates over the `expenses` array and renders each expense as a list item, displaying the description, amount, and category.
- Inline CSS is used for basic styling.
3. ExpenseSummary Component
This component will display a summary of the expenses (total spent). Create a new file named `components/ExpenseSummary.js` and add the following code:
const ExpenseSummary = ({ expenses }) => {
const totalExpenses = expenses.reduce((sum, expense) => sum + expense.amount, 0);
return (
<div className="expense-summary">
<h2>Expense Summary</h2>
<p>Total Expenses: ${totalExpenses.toFixed(2)}</p>
<style jsx>{`
.expense-summary {
margin-top: 20px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
`}</style>
</div>
);
};
export default ExpenseSummary;
Explanation:
- The component takes an `expenses` prop, which is an array of expense objects.
- It calculates the total expenses using the `reduce` method.
- It renders a heading and displays the total expenses.
- Inline CSS is used for basic styling.
Integrating Components in the Main Page
Now, let’s integrate these components into our main page (`pages/index.js`). Replace the content of `pages/index.js` with the following code:
import { useState } from 'react';
import ExpenseForm from '../components/ExpenseForm';
import ExpenseList from '../components/ExpenseList';
import ExpenseSummary from '../components/ExpenseSummary';
const Home = () => {
const [expenses, setExpenses] = useState([]);
const addExpense = (newExpense) => {
setExpenses([...expenses, newExpense]);
};
return (
<div className="container">
<h1>Expense Tracker</h1>
<ExpenseForm onAddExpense={addExpense} />
<ExpenseSummary expenses={expenses} />
<ExpenseList expenses={expenses} />
<style jsx>{`
.container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
h1 {
text-align: center;
}
`}</style>
</div>
);
};
export default Home;
Explanation:
- We import the necessary components: `ExpenseForm`, `ExpenseList`, and `ExpenseSummary`.
- We use the `useState` hook to manage the `expenses` state, initialized as an empty array.
- The `addExpense` function is used to add new expenses to the `expenses` array.
- We render the `ExpenseForm`, `ExpenseSummary`, and `ExpenseList` components and pass the necessary props.
- Basic styling is added using inline CSS.
Testing the Application
Save all the files and go back to your browser. You should now see the expense tracker application. You can add expenses using the form, and they will be displayed in the expense list. The total expenses will also be updated dynamically.
Adding More Features (Optional)
Here are some ideas to enhance your expense tracker:
- Expense Filtering: Add filtering options to filter expenses by category or date range.
- Data Persistence: Store expenses in local storage or a database so the data persists across sessions.
- Expense Editing/Deletion: Implement functionality to edit or delete existing expenses.
- Charts and Graphs: Use a charting library to visualize spending patterns.
- User Authentication: Implement user authentication to allow multiple users to use the application.
Common Mistakes and How to Fix Them
Here are some common mistakes and how to avoid them:
- Incorrect State Updates: When updating state, always use the correct method (e.g., `setExpenses([…expenses, newExpense])`) to ensure that the state is updated correctly.
- Missing Dependencies in useEffect: If you use the `useEffect` hook, make sure to include all dependencies in the dependency array to avoid unexpected behavior.
- Incorrect Data Types: Ensure that you handle data types correctly (e.g., converting the amount to a number using `parseFloat`).
- Not Handling Form Submission Correctly: Always prevent the default form submission behavior using `e.preventDefault()` to avoid page reloads.
Key Takeaways
- You’ve successfully built a basic expense tracker application using Next.js.
- You’ve learned how to create components, manage state, handle user input, and display data dynamically.
- You’ve gained practical experience with Next.js and React concepts.
- You have a foundation to expand and customize the application further.
FAQ
1. How do I deploy this application?
You can deploy your Next.js application to platforms like Vercel (recommended, as it’s built by the Next.js team), Netlify, or other hosting providers. The deployment process usually involves pushing your code to a repository and configuring the platform to build and deploy your application.
2. How can I add data persistence to my expense tracker?
You can add data persistence using local storage (for simple cases) or a database (for more complex applications). For local storage, you can use the `localStorage` API to save and retrieve expenses. For a database, you can use a service like Firebase, MongoDB, or PostgreSQL.
3. How can I improve the styling of my application?
You can improve the styling by using CSS frameworks like Tailwind CSS, Bootstrap, or Material UI, or by writing more detailed CSS. Consider using a CSS-in-JS solution like styled-components for more dynamic styling. Always ensure your design is responsive for different screen sizes.
4. How can I add filtering and sorting capabilities?
To add filtering, you can add input fields or dropdowns to the UI to allow the user to select filter criteria (e.g., category, date range). Then, filter the expenses array based on the selected criteria using JavaScript’s `filter` method. For sorting, use the `sort` method to sort the expenses array based on the desired criteria (e.g., date, amount).
5. What are the best practices for handling errors?
Implement error handling by wrapping your code in `try…catch` blocks to catch and handle potential errors. Display meaningful error messages to the user. Use a logging service to log errors for debugging purposes. Validate user input to prevent common errors.
Building this expense tracker provides a solid foundation for understanding the core concepts of Next.js and React. The ability to create interactive forms, manage data, and dynamically display content is a valuable skill in web development. As you explore more advanced features like filtering, data persistence, and data visualization, you’ll further enhance your understanding and create more sophisticated and useful web applications. Remember, the key to mastering any programming language or framework is practice. So, keep building, experimenting, and exploring the endless possibilities that Next.js offers. With each project, you will not only improve your technical skills, but also gain confidence in your ability to solve real-world problems through code. The journey of a thousand lines of code begins with a single step, and you’ve just taken a significant one.
