In today’s digital age, managing personal finances is more crucial than ever. Keeping track of income and expenses can feel daunting, leading to overspending, missed bills, and financial stress. The good news? Technology offers a straightforward solution: a personal expense tracker. In this guide, we’ll build a simple yet effective expense tracker application using React JS. This project is perfect for beginners and intermediate developers looking to hone their React skills while creating something practical and useful.
Why Build an Expense Tracker?
An expense tracker provides a clear overview of where your money goes. By visualizing your spending habits, you can identify areas where you can save, budget more effectively, and ultimately achieve your financial goals. Moreover, building this application allows you to learn and practice fundamental React concepts such as components, state management, event handling, and conditional rendering, all in a real-world context.
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 computer.
- A code editor like Visual Studio Code, Sublime Text, or Atom.
Step-by-Step Guide to Building the Expense Tracker
1. Setting Up the React Project
Let’s start by creating a new React project using Create React App. Open your terminal and run the following command:
npx create-react-app expense-tracker
cd expense-tracker
This command sets up a new React project named “expense-tracker.” Navigate into the project directory using the ‘cd’ command.
2. Project Structure and Initial Files
Once the project is created, the basic structure will look something like this:
expense-tracker/
├── node_modules/
├── public/
│ ├── index.html
│ └── ...
├── src/
│ ├── App.js
│ ├── App.css
│ ├── index.js
│ └── ...
├── package.json
└── ...
The core of our application will reside in the `src` folder. For this project, we’ll modify `App.js` and create a few new components. Let’s clean up `App.js` by removing the boilerplate code and starting with a basic functional component:
import React from 'react';
import './App.css';
function App() {
return (
<div className="container">
<h1>Expense Tracker</h1>
<p>Welcome to your expense tracker!</p>
</div>
);
}
export default App;
Also, clear the contents of `App.css` for now. We will add styling later.
3. Creating Components
Our expense tracker will consist of several components. Let’s create these:
- **ExpenseForm:** This component will handle the input for new expenses (description, amount, date).
- **ExpenseList:** This component will display a list of expenses.
- **ExpenseItem:** Each expense in the list will be rendered by this component.
- **ExpenseSummary:** This component will display the total expenses.
ExpenseForm Component
Create a new file named `ExpenseForm.js` in the `src` directory. This component will manage the form for adding new expenses. It will use state to manage the input values and handle the form submission.
import React, { useState } from 'react';
function ExpenseForm({ onAddExpense }) {
const [description, setDescription] = useState('');
const [amount, setAmount] = useState('');
const [date, setDate] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!description || !amount || !date) {
alert('Please fill in all fields.');
return;
}
const newExpense = {
id: Math.random().toString(), // Generate a unique ID
description,
amount: parseFloat(amount),
date,
};
onAddExpense(newExpense);
setDescription('');
setAmount('');
setDate('');
};
return (
<form onSubmit={handleSubmit} className="expense-form">
<div className="form-group">
<label htmlFor="description">Description:</label>
<input
type="text"
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<div className="form-group">
<label htmlFor="amount">Amount:</label>
<input
type="number"
id="amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
<div className="form-group">
<label htmlFor="date">Date:</label>
<input
type="date"
id="date"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
</div>
<button type="submit">Add Expense</button>
</form>
);
}
export default ExpenseForm;
This code defines the `ExpenseForm` component. It uses the `useState` hook to manage the form input values (description, amount, and date). The `handleSubmit` function is triggered when the form is submitted. It prevents the default form submission behavior, validates the input, creates a new expense object, calls the `onAddExpense` function passed as a prop (which will update the main app’s state), and clears the form fields.
ExpenseList Component
Create a new file named `ExpenseList.js` in the `src` directory. This component will display the list of expenses.
import React from 'react';
import ExpenseItem from './ExpenseItem';
function ExpenseList({ expenses, onDeleteExpense }) {
return (
<ul className="expense-list">
{expenses.map((expense) => (
<ExpenseItem key={expense.id} expense={expense} onDeleteExpense={onDeleteExpense} />
))}
</ul>
);
}
export default ExpenseList;
This component receives an array of `expenses` and uses the `map` function to render an `ExpenseItem` component for each expense in the array. It also receives `onDeleteExpense` function as a prop to handle deleting expense items.
ExpenseItem Component
Create a new file named `ExpenseItem.js` in the `src` directory. This component will display a single expense item.
import React from 'react';
function ExpenseItem({ expense, onDeleteExpense }) {
const handleDelete = () => {
onDeleteExpense(expense.id);
};
return (
<li className="expense-item">
<div>{expense.description}</div>
<div>${expense.amount.toFixed(2)}</div>
<div>{expense.date}</div>
<button onClick={handleDelete}>Delete</button>
</li>
);
}
export default ExpenseItem;
This component displays the description, amount, and date of a single expense. It also includes a delete button that calls the `onDeleteExpense` function when clicked.
ExpenseSummary Component
Create a new file named `ExpenseSummary.js` in the `src` directory. This component will calculate and display the total expenses.
import React from 'react';
function ExpenseSummary({ expenses }) {
const totalExpenses = expenses.reduce((sum, expense) => sum + expense.amount, 0);
return (
<div className="expense-summary">
<h3>Total Expenses: ${totalExpenses.toFixed(2)}</h3>
</div>
);
}
export default ExpenseSummary;
This component calculates the total expenses by using the `reduce` method on the expenses array. It then displays the total.
4. Integrating Components in App.js
Now, let’s integrate these components into our `App.js` file. This is where we’ll manage the state of our expense data and pass it down to the child components.
import React, { useState } from 'react';
import ExpenseForm from './ExpenseForm';
import ExpenseList from './ExpenseList';
import ExpenseSummary from './ExpenseSummary';
import './App.css';
function App() {
const [expenses, setExpenses] = useState([]);
const addExpense = (newExpense) => {
setExpenses([...expenses, newExpense]);
};
const deleteExpense = (id) => {
setExpenses(expenses.filter(expense => expense.id !== id));
};
return (
<div className="container">
<h1>Expense Tracker</h1>
<ExpenseForm onAddExpense={addExpense} />
<ExpenseSummary expenses={expenses} />
<ExpenseList expenses={expenses} onDeleteExpense={deleteExpense} />
</div>
);
}
export default App;
In this code, we import the components we created. We use the `useState` hook to manage the `expenses` array. The `addExpense` function adds a new expense to the expenses array, and the `deleteExpense` function removes an expense. We pass the `addExpense` function as a prop to `ExpenseForm` and the `expenses` array to `ExpenseList` and `ExpenseSummary`. We also pass the `deleteExpense` function to `ExpenseList` to enable deleting expense items.
5. Styling with CSS
To make our application visually appealing, let’s add some CSS. Open `App.css` and add the following styles:
.container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #f9f9f9;
}
h1 {
text-align: center;
color: #333;
}
.expense-form {
margin-bottom: 20px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #fff;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"], input[type="number"], input[type="date"] {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
margin-bottom: 10px;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #3e8e41;
}
.expense-summary {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #fff;
margin-bottom: 20px;
}
.expense-list {
list-style: none;
padding: 0;
}
.expense-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
background-color: #fff;
}
.expense-item:last-child {
border-bottom: none;
}
These styles provide a basic layout and visual appearance for the application.
6. Running the Application
To run the application, open your terminal, navigate to your project directory (expense-tracker), and run the following command:
npm start
This will start the development server, and your application should open in your browser at `http://localhost:3000/`.
Common Mistakes and How to Fix Them
Here are some common mistakes beginners make when building React applications and how to avoid them:
- **Incorrect State Updates:** One of the most common mistakes is not updating the state correctly. React state updates should be done using the `setState` function provided by the `useState` hook. Directly modifying the state variable will not trigger a re-render. For example, instead of `expenses.push(newExpense)`, use `setExpenses([…expenses, newExpense])`.
- **Forgetting to Pass Props:** Make sure you are correctly passing props from parent to child components. If a child component needs data or functions from its parent, you must pass them as props. For example, if you are passing a function to handle an event, make sure you correctly pass it to the child component.
- **Incorrect Key Prop:** When rendering lists of elements using `map`, always include a unique `key` prop on each element. This helps React efficiently update the DOM. The `key` should be unique across the list. If your data has an id, use that.
- **Not Handling Form Submissions Correctly:** When working with forms, make sure to prevent the default form submission behavior using `e.preventDefault()`. This prevents the page from reloading. Also, ensure you are correctly updating the state with the form values.
- **Incorrect import/export statements:** Check the import/export statements between files, especially if you get errors like “cannot find module”.
Key Takeaways
- React components are the building blocks of React applications.
- The `useState` hook is used to manage component state.
- Props are used to pass data and functions between components.
- Event handling is crucial for user interaction.
- Proper state management is critical for application functionality.
Optional: FAQ
Q: Can I store the expense data in local storage?
A: Yes! You can modify the application to store the expense data in the browser’s local storage. This will allow the data to persist even after the browser is closed. You would need to read from local storage when the component mounts and write to it whenever the expenses state changes. This is a great exercise to learn about persistent data storage.
Q: How can I add expense categories?
A: You can extend the application by adding a category field to the expense form and expense items. You would need to add a dropdown for selecting the category in the form. You would also need to modify the ExpenseItem component to display the category. Further, you can add filtering capabilities to filter the expenses by category.
Q: How can I improve the application’s user interface?
A: You can use a CSS framework like Bootstrap, Material-UI, or Tailwind CSS to quickly improve the application’s styling. You can also add more visual elements, such as charts and graphs, to visualize the expense data.
Q: What are some good practices for managing larger React applications?
A: For larger applications, it’s recommended to use state management libraries like Redux or Zustand. These libraries provide a centralized store for managing application state, making it easier to manage complex state transitions and share state between components. Also, consider using a component library to avoid writing custom CSS.
Q: How can I deploy this application?
A: You can deploy your React application to platforms like Netlify, Vercel, or GitHub Pages. These platforms provide free hosting for static websites. You would typically build your React application using `npm run build` and then deploy the contents of the `build` directory to the hosting platform.
Building a React expense tracker is an excellent way to solidify your understanding of React fundamentals. By breaking down the project into manageable components and focusing on key concepts like state management and event handling, you can create a functional and valuable application. This project not only enhances your React skills but also provides a practical tool for managing your finances. With the knowledge gained from this guide, you can confidently tackle more complex React projects and continue your journey in web development. Remember to practice regularly, experiment with different features, and never stop learning. The world of React is vast and exciting, and with each project, you’ll become a more proficient and capable developer.
