Foundation project
Project: a shopping list app
Lesson 12 of 20 · 22 min · Module 3: State, storage and accessibility
Build a complete little app: add items with a form, tick them off, remove them, and find your list again after a reload. It brings together every technique from the first three modules.
Start this lesson →Free. No installation: you write and run the code in your browser, and it is checked automatically.
By the end, you’ll be able to…
- Combine state, rendering, events and storage in one app
- Identify items with stable ids
- Structure a small program into load, save and render
The idea
Keep the items in an array of objects such as { id, name, qty, bought }, save it to localStorage after every change, and draw the page with one render() function. Use the form’s submit event to add items and delegated listeners on the list for ticking and removing.
const items = [{ id: 1, name: 'Milk', qty: 2, bought: false }];
// <li data-id="1">
// <label><input type="checkbox" class="bought"> 2 × Milk</label>
// <button class="remove" type="button" aria-label="Remove Milk">×</button>
// </li>
01Plan the data first
Write down what one item looks like before writing any code. Every feature then becomes a small change to the array followed by save() and render().
02Stable ids, not positions
Positions change when items are removed, so a listener that remembers "the second item" ends up deleting the wrong one. A unique id stored in data-id always finds the right item.
li.dataset.id = item.id;
// later
const id = Number(event.target.closest('li').dataset.id);
03Three small functions
load() reads and validates storage, save() writes it, and render() draws everything. Listeners only combine them. When something goes wrong, you know exactly which function to look at.
Your project brief
• Data: an array of
{ id, name, qty, bought }, saved as JSON under shopping-list after every change and loaded on start (damaged or missing data means an empty list).• Render: each
<li data-id> contains <label><input type="checkbox" class="bought"> 2 × Milk</label> and <button class="remove" type="button" aria-label="Remove Milk">×</button>; bought items get the class is-bought; #empty is hidden when there are items; #summary shows 3 items, 1 bought (or 1 item, …).• Add: on submit, prevent the default, add the trimmed name and the quantity as a number, reset the form and put focus back on
#item.• Tick and remove: one
change and one click listener on #list.What the checks look for
- The list starts empty
- The form adds an item
- The form resets and focus returns to Item
- Ticking marks an item as bought
- Remove deletes the right item
- The list is saved as JSON
- A saved list comes back
- A damaged save does not break the page
- Item names are shown as text
Take this with you
A small app is data plus load, save, render, and a few listeners.
Go a little further
Add a Clear bought button and sort bought items to the bottom of the list.