Managing finances can be a daunting task. Keeping track of income and expenses, categorizing transactions, and visualizing spending patterns often involves spreadsheets, multiple apps, or complex software. Wouldn’t it be great to have a simple, intuitive tool that simplifies this process? In this tutorial, we will build a dynamic React JS interactive expense tracker. This application will allow users to add expenses, categorize them, and see a summary of their spending habits. You will learn fundamental React concepts, including state management, component composition, and event handling, while creating a practical and useful application.
Why Build an Expense Tracker?
An expense tracker is more than just a personal finance tool; it’s a learning experience. Building one provides hands-on practice with:
- State Management: Understanding how to store and update data within a React application.
- Component Composition: Breaking down a complex UI into reusable, manageable components.
- Event Handling: Responding to user interactions and updating the application accordingly.
- Data Visualization: (Optional) Presenting data in a clear and understandable format.
This project is perfect for beginners to intermediate React developers looking to solidify their understanding of these core concepts. Moreover, it’s a practical application that you can customize and expand upon to meet your specific needs.
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 is necessary to understand the code.
- A code editor: Choose your favorite – VS Code, Sublime Text, Atom, or any other editor will work.
- Create React App (Optional): While not strictly required, using Create React App is the easiest way to get started. It sets up the basic project structure and build configurations for you. If you don’t want to use it, you can manually set up the project, but we will assume you are using Create React App for this tutorial.
Setting Up the Project
Let’s get started by creating a new React project using Create React App:
npx create-react-app expense-tracker
cd expense-tracker
This command creates a new directory named “expense-tracker” and initializes a React project inside it. Navigate into the project directory.
Next, let’s clean up the boilerplate code. Open `src/App.js` and replace the contents with the following:
import React from 'react';
import './App.css';
function App() {
return (
<div>
{/* Your expense tracker components will go here */}
</div>
);
}
export default App;
Also, remove the contents of `src/App.css` and `src/index.css`. We’ll add our own styles later. For now, let’s get the core functionality working.
Component Breakdown
Our expense tracker will consist of several components:
- App.js: The main component that orchestrates the entire application.
- ExpenseForm.js: A form for adding new expenses.
- ExpenseList.js: Displays a list of expenses.
- ExpenseItem.js: Represents a single expense in the list.
- ExpenseSummary.js: Displays a summary of the expenses (total spent, etc.).
Building the ExpenseForm Component
Create a new file named `src/components/ExpenseForm.js`. This component will handle user input for adding new expenses.
import React, { useState } from 'react';
import './ExpenseForm.css';
function ExpenseForm({ onAddExpense }) {
const [description, setDescription] = useState('');
const [amount, setAmount] = useState('');
const [category, setCategory] = useState('food'); // Default category
const handleSubmit = (e) => {
e.preventDefault();
if (!description || !amount) {
alert('Please enter a description and amount.');
return;
}
const newExpense = {
id: Date.now(), // Simple ID generation for now
description,
amount: parseFloat(amount),
category,
};
onAddExpense(newExpense);
setDescription('');
setAmount('');
setCategory('food'); // Reset category
};
return (
<h2>Add Expense</h2>
<div>
<label>Description:</label>
setDescription(e.target.value)}
/>
</div>
<div>
<label>Amount:</label>
setAmount(e.target.value)}
/>
</div>
<div>
<label>Category:</label>
setCategory(e.target.value)}
>
Food
Transportation
Housing
Entertainment
Other
</div>
<button type="submit">Add Expense</button>
);
}
export default ExpenseForm;
This component uses the `useState` hook to manage the form input values (description, amount, and category). The `handleSubmit` function is called when the form is submitted. It prevents the default form submission behavior, creates a new expense object, calls the `onAddExpense` function (which will be passed as a prop from the `App` component), and resets the form fields. Also, create `src/components/ExpenseForm.css` and add some basic styling:
.expense-form {
border: 1px solid #ccc;
padding: 20px;
margin-bottom: 20px;
border-radius: 5px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"], input[type="number"], select {
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;
}
Building the ExpenseList Component
Now, let’s create the `ExpenseList` component, which will display the expenses in a list. Create `src/components/ExpenseList.js`:
import React from 'react';
import ExpenseItem from './ExpenseItem';
import './ExpenseList.css';
function ExpenseList({ expenses, onDeleteExpense }) {
return (
<div>
<h2>Expenses</h2>
{expenses.length === 0 ? (
<p>No expenses added yet.</p>
) : (
<ul>
{expenses.map((expense) => (
))}
</ul>
)}
</div>
);
}
export default ExpenseList;
This component receives an array of `expenses` as a prop and renders an `ExpenseItem` component for each expense. It also handles the case where there are no expenses to display. Create `src/components/ExpenseList.css` and add some basic styling:
.expense-list {
margin-bottom: 20px;
border: 1px solid #ccc;
padding: 20px;
border-radius: 5px;
}
ul {
list-style: none;
padding: 0;
}
Building the ExpenseItem Component
The `ExpenseItem` component represents a single expense item in the list. Create `src/components/ExpenseItem.js`:
import React from 'react';
import './ExpenseItem.css';
function ExpenseItem({ expense, onDeleteExpense }) {
const { description, amount, category } = expense;
return (
<li>
<div>{description}</div>
<div>${amount.toFixed(2)}</div>
<div>{category}</div>
<button> onDeleteExpense(expense.id)}>Delete</button>
</li>
);
}
export default ExpenseItem;
This component displays the expense description, amount, and category. It also includes a delete button that calls the `onDeleteExpense` function (passed as a prop) when clicked. Create `src/components/ExpenseItem.css` and add some basic styling:
.expense-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.expense-item-description {
flex-grow: 1;
}
.expense-item-amount {
font-weight: bold;
}
.expense-item-category {
margin-left: 10px;
font-style: italic;
}
button {
background-color: #f44336;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #d32f2f;
}
Building the ExpenseSummary Component
The `ExpenseSummary` component will display the total expenses and, optionally, other summary information. Create `src/components/ExpenseSummary.js`:
import React from 'react';
import './ExpenseSummary.css';
function ExpenseSummary({ expenses }) {
const totalExpenses = expenses.reduce((sum, expense) => sum + expense.amount, 0);
return (
<div>
<h2>Summary</h2>
<p>Total Expenses: ${totalExpenses.toFixed(2)}</p>
</div>
);
}
export default ExpenseSummary;
This component calculates the total expenses using the `reduce` method and displays the result. Create `src/components/ExpenseSummary.css` and add some basic styling:
.expense-summary {
border: 1px solid #ccc;
padding: 20px;
border-radius: 5px;
margin-bottom: 20px;
}
Putting It All Together: App.js
Now, let’s integrate all the components in `App.js`. This is where we’ll manage the state of the expenses and pass it down to the child components.
import React, { useState } from 'react';
import ExpenseForm from './components/ExpenseForm';
import ExpenseList from './components/ExpenseList';
import ExpenseSummary from './components/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>
<h1>Expense Tracker</h1>
</div>
);
}
export default App;
In this component:
- We use the `useState` hook to manage the `expenses` state, which is an array of expense objects.
- The `addExpense` function is called when a new expense is added through the `ExpenseForm` component. It updates the `expenses` state by adding the new expense to the array.
- The `deleteExpense` function is called when the delete button in the `ExpenseItem` component is clicked. It filters the `expenses` array to remove the expense with the matching ID.
- We pass the `addExpense` function as a prop to `ExpenseForm` and the `expenses` and `deleteExpense` functions as props to `ExpenseList`.
- The `ExpenseSummary` component receives the `expenses` array as a prop.
Finally, add some styling to `src/App.css`:
.App {
max-width: 800px;
margin: 20px auto;
padding: 20px;
font-family: sans-serif;
}
h1 {
text-align: center;
margin-bottom: 30px;
}
Running the Application
To run the application, execute the following command in your terminal:
npm start
This will start the development server and open the application in your browser (usually at `http://localhost:3000`). You should see the expense tracker interface. You can now add expenses, view the list, and see the summary.
Common Mistakes and How to Fix Them
Here are some common mistakes and how to avoid them:
- Incorrect State Updates: When updating state with arrays or objects, always create a new array or object instead of modifying the existing one directly. Use the spread operator (`…`) to create a copy of the array and add or remove items. For example, instead of `expenses.push(newExpense)`, use `setExpenses([…expenses, newExpense])`.
- Forgetting to Pass Props: Make sure you pass the necessary props to your child components. If a component expects a prop, and you don’t pass it, you will get an error. Double-check your component definitions and how you are using them in the parent components.
- Incorrect Event Handling: When handling events, make sure you are passing the correct event handler functions to the elements. For example, in a button’s `onClick` handler, make sure the function is correctly bound. Ensure the function is not being immediately invoked.
- Not Handling Edge Cases: Always consider edge cases, such as empty input fields or invalid data. Validate user input in your form and provide appropriate error messages.
- Styling Issues: Ensure you have properly linked your CSS files and that your CSS selectors are correct. Use your browser’s developer tools to inspect the elements and debug styling issues.
Key Takeaways
- State Management: Understanding how to use the `useState` hook to manage component state.
- Component Composition: Breaking down a UI into reusable components.
- Props: Passing data and functions between components.
- Event Handling: Handling user interactions, such as form submissions and button clicks.
- Lists and Keys: Rendering lists of data using the `map` method and the importance of unique keys.
FAQ
Q: How can I add more categories to the expense tracker?
A: You can easily add more categories by modifying the options in the `ExpenseForm` component’s select element. Add more “ tags with the desired category values.
Q: How can I save the expenses to local storage or a database?
A: To persist the expense data, you can use local storage or a database. For local storage, you would use the `localStorage` API to save the `expenses` array as a JSON string when the application state changes (e.g., when an expense is added or deleted). You would also load the data from local storage when the component mounts. For a database, you would need to set up a backend API to handle the data storage and retrieval. You would then make API calls from your React application to interact with the database.
Q: How can I add more features, such as filtering or sorting expenses?
A: You can add filters and sorting by adding new state variables to manage filter criteria (e.g., category, date range) and sort order. Then, modify the `ExpenseList` component to filter and sort the expenses based on the filter criteria before rendering them. You can add additional input fields or controls in your UI to allow users to specify their filter and sort preferences.
Q: How do I handle date inputs?
A: For date inputs, use a standard HTML5 date input (`type=”date”`). This will provide a date picker. You’ll need to handle the date format correctly (usually converting it to a standard format like ISO 8601) when storing or displaying it.
Next Steps
This expense tracker is a starting point. You can extend it by adding features like date filtering, expense editing, data visualization (charts), and user authentication. Consider refactoring the code into separate modules for better organization. Experiment with different styling approaches and user interface designs to enhance the user experience. The knowledge gained here lays the groundwork for building more complex React applications. Remember that continuous learning and practice are key to mastering React and web development.
