-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
188: Building a Simple Expense Tracker with localStorage
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:
- Add a "Clear All" button to the HTML.
- Write a click event listener for that button that does three things:
- Empties the
expensesarray. - Removes the
myExpenseskey fromlocalStorage(you can uselocalStorage.removeItem()or set it to an empty array). - Calls
renderExpenses()to update the UI.
- Empties the
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.
There are no comments for now.