advanced Step 18 of 20

Local Storage and JSON

JavaScript Programming

Local Storage and JSON

Web applications often need to persist data on the client side — remembering user preferences, caching API responses, saving form progress, or maintaining application state across page reloads. The Web Storage API provides two mechanisms: localStorage (persists until explicitly cleared) and sessionStorage (persists until the browser tab is closed). Since these APIs only store strings, JSON serialization is essential for storing complex data structures like objects and arrays.

Local Storage Basics

// Store a simple string
localStorage.setItem("username", "Alice");

// Retrieve a value
const username = localStorage.getItem("username");
console.log(username);  // "Alice"

// Remove a specific item
localStorage.removeItem("username");

// Clear all stored data
localStorage.clear();

// Check how many items are stored
console.log(localStorage.length);

// Iterate over all keys
for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    console.log(`${key}: ${localStorage.getItem(key)}`);
}

// localStorage only stores strings!
localStorage.setItem("count", 42);
const count = localStorage.getItem("count");
console.log(typeof count);  // "string" — it's "42", not 42
console.log(count + 1);     // "421" — string concatenation!

Storing Complex Data with JSON

// Store objects and arrays as JSON strings
const user = {
    name: "Alice",
    age: 30,
    preferences: { theme: "dark", language: "en" },
    scores: [95, 87, 92]
};

// Save
localStorage.setItem("user", JSON.stringify(user));

// Load
const savedUser = JSON.parse(localStorage.getItem("user"));
console.log(savedUser.name);  // "Alice"
console.log(savedUser.preferences.theme);  // "dark"

// Safe loading with fallback
function loadFromStorage(key, defaultValue = null) {
    try {
        const item = localStorage.getItem(key);
        return item ? JSON.parse(item) : defaultValue;
    } catch (error) {
        console.error(`Error loading ${key}:`, error);
        return defaultValue;
    }
}

const settings = loadFromStorage("settings", { theme: "light" });

Storage Manager Class

class StorageManager {
    constructor(prefix = "app") {
        this.prefix = prefix;
    }

    #key(name) {
        return `${this.prefix}_${name}`;
    }

    set(name, value, ttlMinutes = null) {
        const item = {
            value,
            timestamp: Date.now(),
            ttl: ttlMinutes ? ttlMinutes * 60 * 1000 : null
        };
        localStorage.setItem(this.#key(name), JSON.stringify(item));
    }

    get(name, defaultValue = null) {
        try {
            const raw = localStorage.getItem(this.#key(name));
            if (!raw) return defaultValue;

            const item = JSON.parse(raw);

            // Check TTL
            if (item.ttl && Date.now() - item.timestamp > item.ttl) {
                this.remove(name);
                return defaultValue;
            }

            return item.value;
        } catch {
            return defaultValue;
        }
    }

    remove(name) {
        localStorage.removeItem(this.#key(name));
    }

    clear() {
        const keys = Object.keys(localStorage)
            .filter(k => k.startsWith(this.prefix));
        keys.forEach(k => localStorage.removeItem(k));
    }
}

// Usage
const storage = new StorageManager("myapp");
storage.set("user", { name: "Alice", role: "admin" });
storage.set("cache", apiData, 30);  // Expires in 30 minutes

const user = storage.get("user");
const cached = storage.get("cache", null);  // null if expired

Session Storage and Differences

// sessionStorage — same API, different lifetime
sessionStorage.setItem("temp", "This disappears when tab closes");

// Differences:
// localStorage:
//   - Persists until explicitly cleared
//   - Shared across all tabs of the same origin
//   - ~5-10 MB limit per origin

// sessionStorage:
//   - Cleared when the tab/window is closed
//   - NOT shared between tabs
//   - ~5-10 MB limit per origin

// Listen for storage changes (from OTHER tabs)
window.addEventListener("storage", (event) => {
    console.log(`Key: ${event.key}`);
    console.log(`Old: ${event.oldValue}`);
    console.log(`New: ${event.newValue}`);
    // Useful for syncing state across tabs
});

Practical Example: Persisting App State

// Auto-save form progress
const form = document.querySelector("#registration-form");
const SAVE_KEY = "registration_progress";

// Load saved data on page load
window.addEventListener("DOMContentLoaded", () => {
    const saved = loadFromStorage(SAVE_KEY, {});
    Object.entries(saved).forEach(([name, value]) => {
        const field = form.querySelector(`[name="${name}"]`);
        if (field) field.value = value;
    });
});

// Save on every input change
form.addEventListener("input", (e) => {
    const formData = Object.fromEntries(new FormData(form));
    localStorage.setItem(SAVE_KEY, JSON.stringify(formData));
});

// Clear on successful submit
form.addEventListener("submit", () => {
    localStorage.removeItem(SAVE_KEY);
});
Pro tip: Always wrap JSON.parse() in a try/catch block when reading from localStorage, because the data could be corrupted, manually edited, or in an unexpected format. Consider adding a TTL (time-to-live) mechanism for cached data to prevent serving stale content.

Key Takeaways

  • localStorage persists data across sessions; sessionStorage clears when the tab closes. Both store only strings.
  • Use JSON.stringify() to store objects/arrays and JSON.parse() to retrieve them.
  • Always wrap JSON.parse() in try/catch and provide default values for robustness.
  • The storage event lets you sync data across browser tabs of the same origin.
  • Consider storage limits (~5-10 MB) and use IndexedDB for larger datasets.