Skip to Content
Course content

188: Building a Simple Expense Tracker with localStorage

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, we've built things that vanish the moment you hit the refresh button. It's fine for learning, but it's frustrating for a user. Today, we're going to build a simple Expense Tracker that actually remembers your spending. We'll use localStorage, which is essentially a small key-value store built right into the browser. It's not a replacement for a database, but for a client-side tool like this, it's perfect.

Getting our HTML scaffolding ready

I'm keeping the UI minimal so we can focus on the data flow. We just need two inputs—one for the item name and one for the cost—and a list to display the results. I'll use a simple <ul> for the list. I've already written the basic HTML, so let's dive straight into the logic.

<div id="app">
  <input type="text" id="name" placeholder="What did you buy?">
  <input type="number" id="amount" placeholder="How much?">
  <button id="add-btn">Add Expense</button>
  <ul id="expense-list"></ul>
</div>

Saving expenses to the browser's memory

The goal here is to maintain an array of objects. Every time we add an expense, we push it to that array and then save the entire array to localStorage. I'll start by grabbing my DOM elements and setting up an empty array to hold the data.

const nameInput = document.getElementById('name');
const amountInput = document.getElementById('amount');
const addBtn = document.getElementById('add-btn');
const list = document.getElementById('expense-list');

let expenses = [];

addBtn.addEventListener('click', () => {
  const expense = {
    name: nameInput.value,
    amount: amountInput.value
  };
  expenses.push(expense);
  localStorage.setItem('myExpenses', expenses);
  renderExpenses();
});

Wait, why is my data "[object Object]"?

I just tested this, and I hit a classic wall. When I refreshed the page and looked at the stored data, I didn't see my list of expenses—I saw the string "[object Object],[object Object]". This is a rite of passage when working with localStorage.

The issue is that localStorage only stores strings. When I passed the expenses array directly into setItem, JavaScript tried to be helpful and converted the array to a string using the default .toString() method, which is useless for objects. To fix this, I need to serialize the array into a JSON string using JSON.stringify().

// The corrected save line:
localStorage.setItem('myExpenses', JSON.stringify(expenses));

Loading the data back on refresh

Now that we're saving correctly, we need to make sure the app doesn't start with an empty list every time the page loads. We'll check localStorage as soon as the script runs. Since the data is stored as a JSON string, we have to turn it back into a JavaScript array using JSON.parse().

I always add a fallback empty array || [] because the first time a user visits the site, localStorage.getItem('myExpenses') will return null, and JSON.parse(null) won't give us the array we need to call .push() on later.

// Load existing data from localStorage
const savedData = localStorage.getItem('myExpenses');
if (savedData) {
  expenses = JSON.parse(savedData);
}

function renderExpenses() {
  list.innerHTML = ''; // Clear the list first
  expenses.forEach((item) => {
    const li = document.createElement('li');
    li.textContent = `${item.name}: $${item.amount}`;
    list.appendChild(li);
  });
}

// Initial render
renderExpenses();

Wiring it all together

The logic is now circular: we take user input, update our local state (the array), stringify that state into localStorage, and then re-render the UI. It's a simple pattern, but it's the foundation of how many modern state-management systems work—keeping the data source as the "single source of truth" and letting the UI simply reflect that data.




📋 Practical Task

Add a "Clear All Expenses" Feature

Your task is to extend the Expense Tracker. The app currently allows users to add items, but it doesn't let them start over. Implement the following:

  1. Add a "Clear All" button to the HTML.
  2. Write a click event listener for that button that does three things:
    • Empties the expenses array.
    • Removes the myExpenses key from localStorage (you can use localStorage.removeItem() or set it to an empty array).
    • Calls renderExpenses() to update the UI.

Test it by adding a few items, refreshing the page to ensure they persisted, and then clicking your new button to wipe the slate clean.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.