In this tutorial, we’ll walk through the process of building a personal finance tracker using React for the frontend and Firebase for backend services. This project is perfect for beginners looking to gain hands-on experience with modern web development technologies.
Setting up the Development Environment
Before we dive into coding, let’s set up our development environment:
- Install Node.js and npm (Node Package Manager) from nodejs.org.
- Open your terminal and create a new React project:
npx create-react-app personal-finance-tracker
cd personal-finance-tracker
npm start
Your default browser should open with a new React app running.
Firebase Setup
Now, let’s set up Firebase:
- Go to firebase.google.com and create a new project.
- Install the Firebase SDK in your React project:
npm install firebase
- Create a
src/firebase.js
file and add your Firebase configuration:
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
// Your Firebase configuration object
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
Designing the User Interface
Let’s create a simple UI for our finance tracker. Replace the contents of src/App.js
with:
import React, { useState } from 'react';
function App() {
const [description, setDescription] = useState('');
const [amount, setAmount] = useState('');
const [type, setType] = useState('expense');
const handleSubmit = (e) => {
e.preventDefault();
// We'll implement this later
};
return (
<div className="App">
<h1>Personal Finance Tracker</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Description"
required
/>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount"
required
/>
<select value={type} onChange={(e) => setType(e.target.value)}>
<option value="expense">Expense</option>
<option value="income">Income</option>
</select>
<button type="submit">Add Transaction</button>
</form>
</div>
);
}
export default App;
Implementing Core Functionality
Now, let’s add the ability to track transactions and calculate the balance:
import React, { useState, useEffect } from 'react';
import { db } from './firebase';
import { collection, addDoc, onSnapshot } from 'firebase/firestore';
function App() {
// ... previous state variables ...
const [transactions, setTransactions] = useState([]);
const [balance, setBalance] = useState(0);
useEffect(() => {
const unsubscribe = onSnapshot(collection(db, 'transactions'), (snapshot) => {
const newTransactions = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
setTransactions(newTransactions);
calculateBalance(newTransactions);
});
return () => unsubscribe();
}, []);
const calculateBalance = (transactions) => {
const newBalance = transactions.reduce((acc, transaction) => {
return transaction.type === 'income'
? acc + parseFloat(transaction.amount)
: acc - parseFloat(transaction.amount);
}, 0);
setBalance(newBalance);
};
const handleSubmit = async (e) => {
e.preventDefault();
await addDoc(collection(db, 'transactions'), {
description,
amount: parseFloat(amount),
type
});
setDescription('');
setAmount('');
};
return (
<div className="App">
{/* ... previous JSX ... */}
<h2>Balance: ${balance.toFixed(2)}</h2>
<h3>Transactions:</h3>
<ul>
{transactions.map(transaction => (
<li key={transaction.id}>
{transaction.description} - ${transaction.amount} ({transaction.type})
</li>
))}
</ul>
</div>
);
}
export default App;
Adding User Authentication
To secure our app, let’s add user authentication:
- Enable Authentication in your Firebase project and set up Email/Password sign-in.
- Install the necessary Firebase auth package:
npm install firebase/auth
- Update your
firebase.js
file:
import { getAuth } from 'firebase/auth';
// ... previous code ...
export const auth = getAuth(app);
- Create a new
Login.js
component:
import React, { useState } from 'react';
import { auth } from './firebase';
import { signInWithEmailAndPassword, createUserWithEmailAndPassword } from 'firebase/auth';
function Login({ setUser }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isSignUp, setIsSignUp] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
try {
if (isSignUp) {
await createUserWithEmailAndPassword(auth, email, password);
} else {
await signInWithEmailAndPassword(auth, email, password);
}
} catch (error) {
console.error(error);
alert(error.message);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
<button type="submit">{isSignUp ? 'Sign Up' : 'Login'}</button>
<button type="button" onClick={() => setIsSignUp(!isSignUp)}>
Switch to {isSignUp ? 'Login' : 'Sign Up'}
</button>
</form>
);
}
export default Login;
- Update
App.js
to include authentication:
import React, { useState, useEffect } from 'react';
import { auth } from './firebase';
import { onAuthStateChanged } from 'firebase/auth';
import Login from './Login';
function App() {
// ... previous state variables ...
const [user, setUser] = useState(null);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user);
});
return () => unsubscribe();
}, []);
if (!user) {
return <Login setUser={setUser} />;
}
// ... rest of the component ...
}
export default App;
Enhancing the Application
To make our app more useful, let’s add expense categorization:
- Update the form in
App.js
to include a category field:
<select value={category} onChange={(e) => setCategory(e.target.value)}>
<option value="food">Food</option>
<option value="transportation">Transportation</option>
<option value="entertainment">Entertainment</option>
<option value="utilities">Utilities</option>
<option value="other">Other</option>
</select>
-
Include the category when adding a new transaction.
-
Add a simple pie chart to visualize expenses by category using a library like Chart.js:
npm install react-chartjs-2 chart.js
Create a new component called ExpenseChart.js
:
import React from 'react';
import { Pie } from 'react-chartjs-2';
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
ChartJS.register(ArcElement, Tooltip, Legend);
function ExpenseChart({ transactions }) {
const expensesByCategory = transactions
.filter(t => t.type === 'expense')
.reduce((acc, t) => {
acc[t.category] = (acc[t.category] || 0) + parseFloat(t.amount);
return acc;
}, {});
const data = {
labels: Object.keys(expensesByCategory),
datasets: [
{
data: Object.values(expensesByCategory),
backgroundColor: [
'#FF6384',
'#36A2EB',
'#FFCE56',
'#4BC0C0',
'#9966FF'
]
}
]
};
return <Pie data={data} />;
}
export default ExpenseChart;
Include this chart in your App.js
.
Testing and Debugging
To ensure our app works correctly, let’s add some basic tests:
- Create a new file
App.test.js
:
import { render, screen, fireEvent } from '@testing-library/react';
import App from './App';
test('renders login form when user is not authenticated', () => {
render(<App />);
const emailInput = screen.getByPlaceholderText(/email/i);
const passwordInput = screen.getByPlaceholderText(/password/i);
expect(emailInput).toBeInTheDocument();
expect(passwordInput).toBeInTheDocument();
});
// Add more tests as needed
- Run the tests:
npm test
Deployment
Finally, let’s deploy our app to Firebase Hosting:
- Build your React app:
npm run build
- Install the Firebase CLI:
npm install -g firebase-tools
- Initialize Firebase Hosting:
firebase init hosting
- Deploy your app:
firebase deploy
Conclusion
Congratulations! You’ve built a personal finance tracker using React and Firebase. This project has introduced you to key concepts in modern web development, including:
- React hooks and state management
- Firebase Authentication and Firestore
- Deploying a web application
To further improve your app, consider adding features like:
- Transaction editing and deletion
- More detailed financial reports
- Data export functionality
- Multi-currency support
Remember, the key to becoming a proficient developer is practice and continuous learning. Keep building and expanding on this project to deepen your skills!