Topic 15 / 15
localStorage, Timers & a Mini Project
localStorage — Persistence Without a Server
Key-value storage that survives reloads and browser restarts. Values are strings, so JSON-encode objects:
// save
localStorage.setItem("theme", "dark");
localStorage.setItem("todos", JSON.stringify(todos));
// load
const theme = localStorage.getItem("theme"); // 'dark' or null
const todos = JSON.parse(localStorage.getItem("todos") ?? "[]");
// remove
localStorage.removeItem("theme");
localStorage.clear();Limits: ~5 MB, synchronous, per-origin, and readable by any script on the page — never store tokens or secrets. sessionStorage is identical but cleared when the tab closes.
Timers
const id = setTimeout(() => console.log("once, after 2s"), 2000);
clearTimeout(id); // cancel before it fires
const tick = setInterval(() => console.log("every second"), 1000);
clearInterval(tick);For animations, use requestAnimationFrame instead of intervals — it syncs with the display’s refresh rate.
The Capstone: a Persistent Todo App
This combines state, rendering, events, delegation, and storage — the architecture of every app:
<form id="todo-form">
<input id="todo-input" placeholder="Add a task…" required />
<button>Add</button>
</form>
<ul id="todo-list"></ul>// state — the single source of truth
let todos = JSON.parse(localStorage.getItem("todos") ?? "[]");
function save() {
localStorage.setItem("todos", JSON.stringify(todos));
}
// render — state in, DOM out (rebuild from data every time)
const list = document.querySelector("#todo-list");
function render() {
list.innerHTML = "";
for (const todo of todos) {
const li = document.createElement("li");
li.dataset.id = todo.id;
li.className = todo.done ? "done" : "";
const span = document.createElement("span");
span.textContent = todo.text; // textContent = XSS-safe
const del = document.createElement("button");
del.className = "delete";
del.textContent = "✕";
li.append(span, del);
list.append(li);
}
}
// actions — change state, save, re-render
const form = document.querySelector("#todo-form");
const input = document.querySelector("#todo-input");
form.addEventListener("submit", (e) => {
e.preventDefault();
todos.push({ id: Date.now(), text: input.value.trim(), done: false });
input.value = "";
save();
render();
});
// one delegated listener handles toggle + delete for every row
list.addEventListener("click", (e) => {
const li = e.target.closest("li");
if (!li) return;
const id = Number(li.dataset.id);
if (e.target.closest(".delete")) {
todos = todos.filter(t => t.id !== id);
} else {
todos = todos.map(t => t.id === id ? { ...t, done: !t.done } : t);
}
save();
render();
});
render(); // initial paint from saved stateNotice the architecture: state → render → events mutate state → re-render. That one-way loop is exactly what React formalises — which makes the React series your natural next step, alongside the CSS series for making it beautiful.