ReviseAlgo Logo

Projects

Expense Tracker

Master building a React Expense Tracker, covering financial ledger state systems, transaction inputs, category filters, and balance calculations.

Last Updated: July 15, 2026 12 min read

1. Learning Objectives

In this project, you will build a personal Expense Tracker application. By the end of this project, you will be able to:

  • Implement a transactional ledger system using arrays of objects.
  • Manage signed values (positive income vs negative expenses) inside inputs.
  • Calculate derived financial metrics (total income, total expenses, and balance) dynamically.
  • Filter transactions dynamically by category (e.g. Food, Utilities, Salary).
  • Delete transaction entries immutably in state.

2. Overview

An **Expense Tracker** is a classic project for practicing arithmetic state operations, list updates, forms validations, and derived state calculations. We will build a financial ledger containing transaction lists, balance calculations, and categorized filtering.

3. Why This Project Matters

Expense trackers teach you how to manage structured transactional data:

  • Signed Arithmetic State: You learn how to capture and process signed numeric inputs (positive values for income and negative values for expenses) inside form components.
  • Dynamic Summary Cards: You learn how to derive key summary values (like total balance) dynamically from a single transaction list state array.

4. Real-World Analogy

Think of building an Expense Tracker like **maintaining a checking account checkbook registry**:

  • The Ledger List: The checkbook pages listing every deposit and check written (the transactions array in state).
  • Adding Entries: Writing a new line in the checkbook (appending a transaction object immutably to state).
  • The Balance Summary: Adding up all deposits and subtracting all checks to find your current balance. You don't write a new balance on every page; you calculate it dynamically based on the entries listed (derived state calculation).

5. Core Concepts

Below is a comparison of manual summary state updates and derived calculations:

Property Manual Summary State Updates (Anti-pattern) Derived Calculations (Recommended)
Implementation Storing balance and totalExpenses in separate state variables. Deriving balances directly from the transaction array during rendering: const balance = list.reduce(...).
Synchronization Risk High. If you modify or delete a transaction, you must update multiple state variables, risking errors. None. Summary values update automatically whenever the transactions array changes.
Validation Complexity High; requires updating multiple state setters in correct order. Low; requires updating only the transaction array.

6. Syntax & API Reference

This example shows how to calculate derived balance values dynamically during rendering:

7. Visual Diagram

This diagram displays how transactions update and trigger balance calculations:

8. Live Example — Full Working Code

Below is a complete HTML page demonstrating a working expense tracker with balance calculations:

What just happened? Adding or deleting transaction entries triggers a re-render. Key financial summary values (Income, Expenses, and Balance) are calculated dynamically from the transactions state array, keeping the UI displays in sync.

9. Interactive Playground

Try It Yourself Challenges:

  1. Change the form input so that the user doesn't have to write a negative sign for expenses, instead using radio buttons to select "Income" or "Expense".
  2. Add a target budget limit feature that displays a red warning message when total expenses exceed the target budget.

10. Common Mistakes

Mistake Why it happens Wrong Correct
Concatenating input strings to amounts instead of parsing them Numeric form input values are captured as strings. Adding them directly to calculations without calling parseFloat() causes string concatenation errors (e.g., "10" + "20" = "1020"). const amount = e.target.value;
setBalance(b => b + amount);
const amount = parseFloat(e.target.value);
if (!isNaN(amount)) { ... }
Storing derived ledger summaries in state Storing values in state that can be calculated from existing state variables (like balances or category subtotals) increases code complexity and risks states getting out of sync. Declaring const [balance, setBalance] = useState(0) and updating it manually whenever the transactions list changes. Calculate the balance dynamically from the transactions state array: const balance = transactions.reduce(...).

11. Best Practices

  • Parse input numbers: Always use parseFloat or parseInt to convert numeric form input string values to numbers.
  • Derive financial metrics: Calculate balances, income totals, and expense totals dynamically from the transaction list state array during rendering.
  • Verify calculations: Add checks (like isNaN validation) to prevent invalid inputs from breaking calculations.
  • Format currency displays: Use toFixed(2) to format financial currency outputs consistently.

12. Browser Compatibility/Requirements

This project runs on all modern browsers supporting standard JavaScript array methods.

13. Interview Questions

Q1: How do you parse and validate numeric input values from form submissions in React?

Answer: Numeric input values are captured as strings from target elements (e.g. `e.target.value`). Parse the value using `parseFloat(value)` or `parseInt(value, 10)`, and validate it using `isNaN(parsedValue)` before using it in calculations or state updates.

Q2: Why should we avoid storing derived values (like total expenses) in separate state variables?

Answer: Storing derived values in separate state variables increases code complexity and risks states getting out of sync. For example, if you delete a transaction, you must update the transactions array, the total expenses, and the balance state variables. Calculating derived values dynamically from the transactions array during rendering ensures the UI displays are always accurate.

14. Debugging Exercise

Identify why adding a new income transaction of $500 changes the available balance display to "Available Balance: $1000500" instead of "$1500":

View Solution

Diagnosis: The input value is captured as a string ('500'). Adding it directly to the numeric balance (balance + inputVal) triggers string concatenation (1000 + '500' = '1000500') instead of addition.

Fix: Parse the input string using parseFloat() before adding it to the balance:

15. Practice Exercises

Exercise 1: Create a monthly budget progress bar

Extend the Expense Tracker by adding a monthly budget input field. Render a progress bar indicating the percentage of the budget spent. Color the bar red when expenses exceed the budget limit.

16. Scenario-Based Challenge

The Multi-currency Ledger conversions challenge:

You are designing a ledger for international transactions. Users can add expenses in USD, EUR, or JPY. The dashboard should display individual sub-totals for each currency and a unified balance converted to USD. Describe how you would implement this using derived states and conversion rates data.

17. Quick Quiz

Q1: Which method should be used to parse float string inputs into numbers safely?

A) Number.parseInt()

B) Number.parseFloat()

C) Math.floor()

Answer: B — Number.parseFloat() parses a string argument and returns a floating-point number, enabling calculations on form inputs.

18. Summary & Key Takeaways

  • Parse input string values to numbers using parseFloat() before using them in calculations.
  • Derive summaries (Income, Expenses, Balance) dynamically from the transaction array during rendering.
  • Always use non-mutating methods to update array states in React.
  • Ensure all elements rendered in a list have unique key props.

19. Cheat Sheet

Ledger Calculation Syntax Example
parseFloat(e.target.value) Parses numeric input string values to floats.
transactions.filter(t => t.amount > 0) Filters the array to select only income transactions.
val.toFixed(2) Formats numeric outputs to show two decimal places.
---