Topic 15 / 15

localStorage, Timers & a Mini Project

~11 min read  //  JavaScript Series  //  Coding India

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 state

Notice 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.