Open In App

Create a Personal Budgeting App using React and D3.js

Last Updated : 26 Feb, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

In this article, we’ll explore how to build a Personal Budgeting App using React for the frontend and D3.js for data visualization. A Personal Budgeting App can provide valuable insights into spending habits, help set financial goals, and track progress over time.

Output Preview: Let us have a look at how the final output will look like.

budget

Prerequisites:

Steps to Create Personal Budgeting React App:

Step 1: Create a new React JS project using the following command.

npx create-react-app <<Project_Name>>

Step 2: Change to the project directory

cd <<Project_Name>>

Step 3: Install the requires modules

npm install d3

Step 4: Create a folder called components in src directory and create the following files inside it Add IncomeTracker.js , ExpenseTracker.js and Visualization.js

Project Structure:

bscr

The updated dependencies in package.json will look like this:

"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.3.2",
"d3": "^7.8.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}

Approach to create Personal Budgeting App:

  • App.js: The App.js file represents the main component of a Personal Budgeting App created with React. It manages income and expense data using state hooks. It includes forms for adding income and expenses and displays them in separate lists. The App component coordinates these components and handles data flow between them.
  • IncomeTracker.js: Manages the form for tracking income transactions. It captures user input for the amount and description of income and sends the data to the parent component.
  • ExpenseTracker.js: Handles the form for tracking expense transactions. It collects user input for the amount and description of expenses and passes the data to the parent component.
  • Visualization.js: Displays visual representations of financial data using D3.js. It updates dynamically based on changes in income, expense, or budget data, providing users with insights into their financial status.

Javascript




// App.js
 
import React,
{
    useState,
    useEffect
} from 'react';
import './App.css';
// Import your CSS file
import ExpenseTracker
    from './Components/ExpenseTracker';
import IncomeTracker
    from './Components/IncomeTracker';
import Visualization
    from './Components/Visualization';
 
function App() {
    const [expenses, setExpenses] = useState([]);
    const [incomes, setIncomes] = useState([]);
    const [totalIncome, setTotalIncome] = useState(0);
    const [totalExpenditure, setTotalExpenditure] = useState(0);
    const [remainingIncome, setRemainingIncome] = useState(0);
    const [percentageExpended, setPercentageExpended] = useState(0);
 
    useEffect(() => {
        // Calculate total income
        let total = 0;
        incomes.forEach(
            (income) =>
                (total += income.amount)
        );
        setTotalIncome(total);
 
        // Calculate total expenditure
        total = 0;
        expenses.forEach(
            (expense) =>
                (total += expense.amount)
        );
        setTotalExpenditure(total);
 
        // Calculate remaining income
        setRemainingIncome(totalIncome - totalExpenditure);
 
        // Calculate percentage expended
        if (totalIncome !== 0) {
            setPercentageExpended(
                (totalExpenditure / totalIncome) * 100
            );
        }
    }, [incomes, expenses, totalIncome, totalExpenditure]);
 
    const addExpense = (expense) => {
        setExpenses([...expenses, expense]);
    };
 
    const addIncome = (income) => {
        // Add the income and reset expenses for each income
        setIncomes([...incomes, income]);
        setExpenses([]);
    };
 
    return (
        <div className="App">
            <h1>Personal Budgeting App</h1>
            <div className="tracker-container">
                <ExpenseTracker onAddExpense={addExpense} />
                <IncomeTracker onAddIncome={addIncome} />
            </div>
            <Visualization
                expenses={expenses}
                incomes={incomes}
                remainingIncome={remainingIncome}
                totalExpenditure={totalExpenditure}
                percentageExpended={percentageExpended}
            />
        </div>
    );
}
 
export default App;


Javascript




// IncomeTracker.js
 
import React, { useState } from 'react';
 
function IncomeTracker({ onAddIncome }) {
    const [amount, setAmount] = useState('');
    const [description, setDescription] = useState('');
 
    const handleAddIncome = () => {
        if (!amount || !description) return;
        onAddIncome(
            {
                amount: parseFloat(amount),
                description
            });
        setAmount('');
        setDescription('');
    };
 
    return (
        <div className="income-tracker">
            <h2>Income Tracking</h2>
            <input
                type="number"
                placeholder="Amount"
                value={amount}
                onChange={
                    (e) =>
                        setAmount(e.target.value)
                } />
            <input
                type="text"
                placeholder="Description"
                value={description}
                onChange={(e) => setDescription(e.target.value)} />
            <button onClick={handleAddIncome}>
                Add Income
            </button>
        </div>
    );
}
 
export default IncomeTracker;


Javascript




// ExpenseTracker.js
 
import React,
{
    useState
} from 'react';
 
function ExpenseTracker({ onAddExpense }) {
    const [description, setDescription] = useState('');
    const [amount, setAmount] = useState('');
 
    const handleAddExpense = () => {
        if (!description || !amount) return;
        const newExpense = {
            description,
            amount: parseFloat(amount)
        };
        onAddExpense(newExpense);
        setDescription('');
        setAmount('');
    };
 
    return (
        <div className="tracker">
            <h2>Add Expense</h2>
            <div className="input-group">
                <input
                    type="text"
                    placeholder="Description"
                    value={description}
                    onChange={
                        (e) =>
                            setDescription(e.target.value)
                    } />
                <input
                    type="number"
                    placeholder="Amount"
                    value={amount}
                    onChange={
                        (e) =>
                            setAmount(e.target.value)
                    } />
                <button onClick={handleAddExpense}>
                    Add Expense
                </button>
            </div>
        </div>
    );
}
 
export default ExpenseTracker;


Javascript




// Visualization.js
import React,
{
    useEffect,
    useRef
} from 'react';
import * as d3 from 'd3';
 
function Visualization(
    {
        expenses,
        incomes,
        remainingIncome,
        totalExpenditure,
        percentageExpended
    }) {
    const svgRef = useRef();
 
    useEffect(() => {
        // Create a combined dataset of incomes and expenses
        const data = incomes.map((income) => ({
            type: 'Income',
            description: `Income: ${income.description}`,
            amount: income.amount,
        })).concat(expenses.map((expense) => ({
            type: 'Expense',
            description: `Expense: ${expense.description}`,
            amount: expense.amount,
        })));
 
        // Clear previous chart
        d3.select(svgRef.current).selectAll('*').remove();
 
        // Create a new chart
        const svg = d3.select(svgRef.current);
 
        const width = 400;
        const height = 200;
 
        const xScale = d3.scaleBand().domain(
            data.map((d) => d.description))
            .range([0, width])
            .padding(0.1);
        const yScale =
            d3.scaleLinear()
                .domain(
                    [
                        0,
                        d3.max(data, (d) => d.amount)
                    ]).range([height, 0]);
 
        svg.append('g').attr('transform', `translate(50, 0)`)
            .call(d3.axisLeft(yScale));
        svg.append('g').attr('transform', `translate(50, ${height})`)
            .call(d3.axisBottom(xScale));
 
        svg.selectAll('.bar')
            .data(data)
            .enter()
            .append('rect')
            .attr('class',
                (d) => (d.type === 'Income' ?
                    'income-bar' : 'expense-bar'))
            .attr('x', (d) => xScale(d.description) + 50)
            .attr('y', (d) => yScale(d.amount))
            .attr('width', xScale.bandwidth())
            .attr('height', (d) => height - yScale(d.amount));
 
    }, [expenses, incomes]);
 
    return (
        <div className="visualization">
            <h2>Financial Visualization</h2>
            <p>
                Total Expenditure:
                ${totalExpenditure.toFixed(2)}
            </p>
            <p>
                Remaining Income:
                ${remainingIncome.toFixed(2)}
            </p>
            <p>
                Percentage Expended:
                {percentageExpended.toFixed(2)}%
            </p>
            <svg ref={svgRef}
                width={500} height={250}></svg>
        </div>
    );
}
 
export default Visualization;


CSS




/* App.css */
 
body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f8f9fa;
}
 
.income-tracker>button {
    background-color: blue;
    padding: 10px 10px;
    color: white;
    border-radius: 5px;
}
 
.income-tracker>input {
    border: 2px solid rgb(161, 157, 157);
    margin: 2px 6px;
}
 
 
 
.App {
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
    background-color: #ffffff;
    /* White background */
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    /* Subtle box shadow */
    border-radius: 8px;
}
 
h1 {
    text-align: center;
    color: #007bff;
    /* Blue color for headings */
}
 
.tracker-container {
    display: flex;
    justify-content: space-around;
    margin-top: 20px;
}
 
.tracker-container>div {
    flex: 1;
    margin: 0 10px;
}
 
.input-group {
    margin-bottom: 20px;
}
 
.input-group label {
    display: block;
    margin-bottom: 8px;
    color: #31aa6d;
}
 
.input-group input,
.input-group select {
    width: 100%;
    padding: 10px;
    font-size: 16px;
    border: 1px solid #ced4da;
    /* Light gray border */
    border-radius: 4px;
    box-sizing: border-box;
}
 
.input-group button {
    width: 100%;
    padding: 12px;
    font-size: 16px;
    background-color: #007bff;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.3s ease;
    /* Smooth background color transition */
}
 
.input-group button:hover {
    background-color: #0056b3;
}
 
.visualization {
    margin-top: 30px;
    padding: 20px;
    background-color: #ffffff;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    border-radius: 8px;
}
 
.visualization h2 {
    color: #007bff;
    margin-bottom: 15px;
}
 
.visualization p {
    margin: 10px 0;
    color: #555;
}
 
svg {
    margin-top: 20px;
    display: block;
    margin-left: auto;
    margin-right: auto;
}


Steps to Run the project:

npm start

Output: Open web-browser and type the following URL http://localhost:3000/

2002project

Output



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads