Labs ICT
Pro Login

Local Storage

Local Storage is a web browser API that allows websites to store data persistently in a user's browser. Unlike cookies, local storage has a larger capacity (typically 5-10MB) and doesn't send data to the server with every HTTP request, making it ideal for client-side data persistence.

In this tutorial, we'll explore the localStorage API, its methods, use cases, limitations, security considerations, and best practices for storing data on the client side.

Understanding Local Storage

Local Storage provides key-value pair storage that persists across browser sessions.

Basic Concepts

// Local Storage characteristics
// - Stores data as strings
// - Persistent across browser sessions
// - Same-origin policy (only accessible from same domain)
// - Typically 5-10MB storage limit
// - Synchronous API (blocking)
// - No expiration time (manual deletion required)

// Check if localStorage is available
if (typeof Storage !== 'undefined') {
  console.log('Local Storage is available');
} else {
  console.log('Local Storage is not supported');
}

// Check localStorage specifically
if (window.localStorage) {
  console.log('localStorage is supported');
}

// Storage capacity estimation
function estimateStorageQuota() {
  try {
    // Try to store increasingly large data
    let testString = 'x';
    for (let i = 0; i < 10000; i++) {
      try {
        localStorage.setItem('test', testString.repeat(i));
      } catch (e) {
        console.log(`Storage limit reached at ${i} characters`);
        localStorage.removeItem('test');
        return i;
      }
    }
  } catch (e) {
    console.error('Storage quota test failed:', e);
  }
  return null;
}

Storage Events

// Listen for storage changes
window.addEventListener('storage', function(event) {
  console.log('Storage changed');
  console.log('Key:', event.key);
  console.log('Old value:', event.oldValue);
  console.log('New value:', event.newValue);
  console.log('URL:', event.url);
  console.log('Storage area:', event.storageArea); // 'localStorage' or 'sessionStorage'
});

// Storage event doesn't fire on the same tab that made the change
// It only fires on other tabs/windows of the same origin

// Cross-tab communication using storage events
function broadcastMessage(message) {
  const key = 'message_' + Date.now();
  localStorage.setItem(key, JSON.stringify(message));
  
  // Clean up old messages
  setTimeout(() => {
    localStorage.removeItem(key);
  }, 100);
}

// Listen for broadcasted messages
window.addEventListener('storage', function(event) {
  if (event.key.startsWith('message_')) {
    const message = JSON.parse(event.newValue);
    console.log('Received message:', message);
  }
});

localStorage Methods

Core methods for interacting with localStorage.

Basic Operations

// setItem() - Store data
localStorage.setItem('username', 'john_doe');
localStorage.setItem('theme', 'dark');
localStorage.setItem('preferences', JSON.stringify({
  language: 'en',
  timezone: 'UTC',
  notifications: true
}));

// getItem() - Retrieve data
const username = localStorage.getItem('username');
console.log(username); // 'john_doe'

const theme = localStorage.getItem('theme');
console.log(theme); // 'dark'

const preferences = JSON.parse(localStorage.getItem('preferences') || '{}');
console.log(preferences.language); // 'en'

// removeItem() - Remove specific data
localStorage.removeItem('username');
localStorage.removeItem('theme');

// clear() - Remove all data
localStorage.clear();

// length property - Get number of items
console.log(localStorage.length); // Number of stored items

// key() method - Get key by index
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  const value = localStorage.getItem(key);
  console.log(`${key}: ${value}`);
}

Direct Property Access

// localStorage can be accessed like an object
localStorage.username = 'john_doe';
localStorage.theme = 'dark';

// Get values
console.log(localStorage.username); // 'john_doe'
console.log(localStorage.theme); // 'dark'

// Delete values
delete localStorage.username;
delete localStorage.theme;

// Check if key exists
if ('username' in localStorage) {
  console.log('Username exists');
}

// Iterate over localStorage
for (const key in localStorage) {
  if (localStorage.hasOwnProperty(key)) {
    console.log(`${key}: ${localStorage[key]}`);
  }
}

// Object.keys() method
Object.keys(localStorage).forEach(key => {
  console.log(`${key}: ${localStorage.getItem(key)}`);
});

// Object.entries() method
Object.entries(localStorage).forEach(([key, value]) => {
  console.log(`${key}: ${value}`);
});

Data Types and Serialization

LocalStorage only stores strings, so data must be serialized properly.

Working with Objects

// Store objects using JSON
const user = {
  id: 123,
  name: 'John Doe',
  email: 'john@example.com',
  preferences: {
    theme: 'dark',
    language: 'en'
  }
};

// Store object
localStorage.setItem('user', JSON.stringify(user));

// Retrieve object
const storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.name); // 'John Doe'
console.log(storedUser.preferences.theme); // 'dark'

// Safe object retrieval
function getObject(key, defaultValue = null) {
  try {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : defaultValue;
  } catch (error) {
    console.error(`Error parsing ${key}:`, error);
    return defaultValue;
  }
}

// Safe object storage
function setObject(key, object) {
  try {
    localStorage.setItem(key, JSON.stringify(object));
    return true;
  } catch (error) {
    console.error(`Error storing ${key}:`, error);
    return false;
  }
}

// Usage
const user = getObject('user', { name: 'Guest' });
setObject('user', { name: 'Jane Doe', id: 456 });

Working with Arrays

// Store arrays
const tasks = [
  { id: 1, text: 'Learn JavaScript', completed: true },
  { id: 2, text: 'Build project', completed: false },
  { id: 3, text: 'Deploy to production', completed: false }
];

localStorage.setItem('tasks', JSON.stringify(tasks));

// Retrieve arrays
const storedTasks = JSON.parse(localStorage.getItem('tasks') || '[]');
console.log(storedTasks.length); // 3

// Array utility functions
function getArray(key, defaultValue = []) {
  try {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : defaultValue;
  } catch (error) {
    console.error(`Error parsing array ${key}:`, error);
    return defaultValue;
  }
}

function setArray(key, array) {
  try {
    localStorage.setItem(key, JSON.stringify(array));
    return true;
  } catch (error) {
    console.error(`Error storing array ${key}:`, error);
    return false;
  }
}

// Add item to stored array
function addToArray(key, item) {
  const array = getArray(key);
  array.push(item);
  setArray(key, array);
}

// Remove item from stored array
function removeFromArray(key, predicate) {
  const array = getArray(key);
  const filtered = array.filter(item => !predicate(item));
  setArray(key, filtered);
}

// Usage
addToArray('tasks', { 
  id: 4, 
  text: 'Write documentation', 
  completed: false 
});

removeFromArray('tasks', task => task.id === 2);

Practical Use Cases

Common scenarios where localStorage is particularly useful.

User Preferences

// User preferences manager
class Preferences {
  constructor() {
    this.defaults = {
      theme: 'light',
      language: 'en',
      fontSize: '16',
      autoSave: true,
      notifications: true
    };
  }
  
  get(key) {
    const value = localStorage.getItem(`pref_${key}`);
    return value !== null ? value : this.defaults[key];
  }
  
  set(key, value) {
    localStorage.setItem(`pref_${key}`, value);
  }
  
  getAll() {
    const prefs = { ...this.defaults };
    
    Object.keys(this.defaults).forEach(key => {
      prefs[key] = this.get(key);
    });
    
    return prefs;
  }
  
  reset() {
    Object.keys(this.defaults).forEach(key => {
      localStorage.removeItem(`pref_${key}`);
    });
  }
}

// Usage
const prefs = new Preferences();

// Set preferences
prefs.set('theme', 'dark');
prefs.set('fontSize', '18');

// Get preferences
console.log(prefs.get('theme')); // 'dark'
console.log(prefs.getAll()); // All preferences

// Apply preferences to UI
function applyPreferences() {
  const allPrefs = prefs.getAll();
  
  document.body.className = allPrefs.theme;
  document.documentElement.style.fontSize = allPrefs.fontSize + 'px';
  
  if (allPrefs.autoSave) {
    enableAutoSave();
  }
}

applyPreferences();

Shopping Cart

// Shopping cart using localStorage
class ShoppingCart {
  constructor() {
    this.key = 'shopping_cart';
  }
  
  getItems() {
    return JSON.parse(localStorage.getItem(this.key) || '[]');
  }
  
  addItem(product, quantity = 1) {
    const items = this.getItems();
    const existingItem = items.find(item => item.id === product.id);
    
    if (existingItem) {
      existingItem.quantity += quantity;
    } else {
      items.push({
        ...product,
        quantity,
        addedAt: new Date().toISOString()
      });
    }
    
    localStorage.setItem(this.key, JSON.stringify(items));
    this.notifyUpdate();
  }
  
  removeItem(productId) {
    const items = this.getItems();
    const filtered = items.filter(item => item.id !== productId);
    localStorage.setItem(this.key, JSON.stringify(filtered));
    this.notifyUpdate();
  }
  
  updateQuantity(productId, quantity) {
    const items = this.getItems();
    const item = items.find(item => item.id === productId);
    
    if (item) {
      item.quantity = Math.max(1, quantity);
      localStorage.setItem(this.key, JSON.stringify(items));
      this.notifyUpdate();
    }
  }
  
  getTotal() {
    const items = this.getItems();
    return items.reduce((total, item) => {
      return total + (item.price * item.quantity);
    }, 0);
  }
  
  clear() {
    localStorage.removeItem(this.key);
    this.notifyUpdate();
  }
  
  notifyUpdate() {
    // Custom event or callback
    window.dispatchEvent(new CustomEvent('cartUpdate', {
      detail: { items: this.getItems(), total: this.getTotal() }
    }));
  }
}

// Usage
const cart = new ShoppingCart();

cart.addItem({ id: 1, name: 'Product 1', price: 19.99 });
cart.addItem({ id: 2, name: 'Product 2', price: 29.99 }, 2);

console.log('Cart total:', cart.getTotal()); // 79.97

// Listen for cart updates
window.addEventListener('cartUpdate', function(event) {
  console.log('Cart updated:', event.detail);
  updateCartUI(event.detail);
});

Error Handling and Edge Cases

Handling errors and edge cases when working with localStorage.

Storage Quota Exceeded

// Handle storage quota errors
function safeSetItem(key, value) {
  try {
    localStorage.setItem(key, value);
    return true;
  } catch (error) {
    if (error.name === 'QuotaExceededError' || 
        error.name === 'NS_ERROR_DOM' || 
        error.code === 22) {
      console.error('Storage quota exceeded');
      handleQuotaExceeded();
      return false;
    } else {
      console.error('Storage error:', error);
      return false;
    }
  }
}

function handleQuotaExceeded() {
  // Try to free up space
  clearOldData();
  
  // Notify user
  showNotification('Storage is full. Please clear some data.');
  
  // Fallback to memory storage
  enableMemoryFallback();
}

function clearOldData() {
  // Remove old cache data
  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - 7); // 7 days ago
  
  Object.keys(localStorage).forEach(key => {
    if (key.startsWith('cache_')) {
      const data = JSON.parse(localStorage.getItem(key));
      if (new Date(data.timestamp) < cutoffDate) {
        localStorage.removeItem(key);
      }
    }
  });
}

// Check available space
function getStorageUsage() {
  let total = 0;
  
  for (let key in localStorage) {
    if (localStorage.hasOwnProperty(key)) {
      total += localStorage[key].length + key.length;
    }
  }
  
  return {
    used: total,
    // Rough estimate (actual quota varies by browser)
    available: 5000000 - total, // Assume 5MB limit
    percentage: (total / 5000000) * 100
  };
}

Privacy Mode and Disabled Storage

// Check if localStorage is available
function isLocalStorageAvailable() {
  try {
    const test = '__test__';
    localStorage.setItem(test, test);
    localStorage.removeItem(test);
    return true;
  } catch (e) {
    return false;
  }
}

// Handle disabled localStorage
if (!isLocalStorageAvailable()) {
  console.log('localStorage is not available');
  
  // Use fallback
  enableMemoryStorage();
  
  // Or notify user
  showStorageDisabledMessage();
}

// Memory storage fallback
const memoryStorage = {};

function enableMemoryStorage() {
  window.localStorage = {
    getItem: function(key) {
      return memoryStorage[key] || null;
    },
    setItem: function(key, value) {
      memoryStorage[key] = value;
    },
    removeItem: function(key) {
      delete memoryStorage[key];
    },
    clear: function() {
      Object.keys(memoryStorage).forEach(key => {
        delete memoryStorage[key];
      });
    },
    get length() {
      return Object.keys(memoryStorage).length;
    },
    key: function(index) {
      return Object.keys(memoryStorage)[index] || null;
    }
  };
}

// Unified storage interface
class StorageManager {
  constructor() {
    this.isAvailable = isLocalStorageAvailable();
    this.storage = this.isAvailable ? localStorage : memoryStorage;
  }
  
  getItem(key) {
    return this.storage.getItem(key);
  }
  
  setItem(key, value) {
    return this.storage.setItem(key, value);
  }
  
  removeItem(key) {
    return this.storage.removeItem(key);
  }
  
  clear() {
    return this.storage.clear();
  }
}

const storage = new StorageManager();
// Use storage regardless of localStorage availability

Security Considerations

Important security considerations when using localStorage.

Data Security

// Never store sensitive data
// BAD: Storing passwords or tokens
localStorage.setItem('password', 'user_password'); // NEVER DO THIS
localStorage.setItem('api_token', 'secret_token'); // NEVER DO THIS

// GOOD: Store non-sensitive preferences
localStorage.setItem('theme', 'dark');
localStorage.setItem('language', 'en');

// Encrypt sensitive data if absolutely necessary
function encryptData(data, key) {
  // Use a proper encryption library
  return btoa(JSON.stringify(data)); // Basic encoding, not encryption
}

function decryptData(encryptedData, key) {
  try {
    return JSON.parse(atob(encryptedData));
  } catch (e) {
    return null;
  }
}

// Store encrypted data
const sensitiveData = {
  sessionId: 'abc123',
  userPreferences: { /* ... */ }
};

const encrypted = encryptData(sensitiveData, 'encryption_key');
localStorage.setItem('encrypted_data', encrypted);

// Retrieve and decrypt
const decrypted = decryptData(localStorage.getItem('encrypted_data'), 'encryption_key');

// Clear sensitive data on logout
function secureLogout() {
  // Clear all localStorage
  localStorage.clear();
  
  // Or be selective
  const keysToRemove = [];
  Object.keys(localStorage).forEach(key => {
    if (key.includes('token') || key.includes('session')) {
      keysToRemove.push(key);
    }
  });
  
  keysToRemove.forEach(key => localStorage.removeItem(key));
}

XSS Prevention

// Sanitize data before storing
function sanitizeForStorage(data) {
  if (typeof data === 'string') {
    // Basic HTML sanitization
    return data
      .replace(//g, '>')
      .replace(/"/g, '"')
      .replace(/'/g, ''');
  }
  return data;
}

// Validate data on retrieval
function validateStorageData(data, schema) {
  if (typeof data !== 'object') return false;
  
  return Object.keys(schema).every(key => {
    if (schema[key].required && !(key in data)) {
      return false;
    }
    
    if (schema[key].type && typeof data[key] !== schema[key].type) {
      return false;
    }
    
    return true;
  });
}

// Safe storage with validation
const userSchema = {
  name: { type: 'string', required: true },
  age: { type: 'number', required: false },
  preferences: { type: 'object', required: false }
};

function setValidatedUser(userData) {
  if (validateStorageData(userData, userSchema)) {
    localStorage.setItem('user', JSON.stringify(userData));
    return true;
  } else {
    console.error('Invalid user data');
    return false;
  }
}

// Content Security Policy considerations
// Ensure your CSP allows localStorage access if needed
// Content-Security-Policy: script-src 'self'; storage-access 'self'

Performance Optimization

Optimizing localStorage usage for better performance.

Efficient Data Management

// Minimize storage operations
class OptimizedStorage {
  constructor() {
    this.cache = {};
    this.dirty = new Set();
  }
  
  get(key) {
    if (key in this.cache) {
      return this.cache[key];
    }
    
    const value = localStorage.getItem(key);
    this.cache[key] = value;
    return value;
  }
  
  set(key, value) {
    this.cache[key] = value;
    this.dirty.add(key);
  }
  
  flush() {
    if (this.dirty.size === 0) return;
    
    // Batch write operations
    this.dirty.forEach(key => {
      localStorage.setItem(key, this.cache[key]);
    });
    
    this.dirty.clear();
  }
  
  // Auto-flush periodically
  startAutoFlush(interval = 1000) {
    setInterval(() => this.flush(), interval);
  }
}

// Compress data before storage
function compressData(data) {
  // Simple compression for demonstration
  // Use proper compression libraries in production
  const json = JSON.stringify(data);
  return btoa(json); // Base64 encoding
}

function decompressData(compressed) {
  try {
    const json = atob(compressed);
    return JSON.parse(json);
  } catch (e) {
    return null;
  }
}

// Store compressed data
const largeData = {
  // Large dataset
};

const compressed = compressData(largeData);
localStorage.setItem('large_data', compressed);

// Retrieve and decompress
const decompressed = decompressData(localStorage.getItem('large_data'));

// Use efficient data structures
// Store as array of objects instead of object of objects
const usersArray = [
  { id: 1, name: 'John' },
  { id: 2, name: 'Jane' }
]; // More efficient for iteration

// Instead of:
const usersObject = {
  '1': { id: 1, name: 'John' },
  '2': { id: 2, name: 'Jane' }
}; // Less efficient for iteration

Storage Monitoring

// Monitor storage usage
class StorageMonitor {
  constructor() {
    this.usage = this.calculateUsage();
    this.limit = this.estimateLimit();
  }
  
  calculateUsage() {
    let total = 0;
    
    for (let key in localStorage) {
      if (localStorage.hasOwnProperty(key)) {
        total += (key + localStorage[key]).length * 2; // UTF-16
      }
    }
    
    return total;
  }
  
  estimateLimit() {
    // Try to detect actual limit
    try {
      let testString = 'x';
      let size = 1;
      
      while (true) {
        try {
          localStorage.setItem('quota_test', testString.repeat(size));
          size *= 2;
        } catch (e) {
          localStorage.removeItem('quota_test');
          return size * testString.length;
        }
      }
    } catch (e) {
      // Fallback estimate
      return 5 * 1024 * 1024; // 5MB
    }
  }
  
  getUsagePercentage() {
    return (this.usage / this.limit) * 100;
  }
  
  isNearLimit(threshold = 90) {
    return this.getUsagePercentage() > threshold;
  }
}

const monitor = new StorageMonitor();

// Check usage before storing large data
function storeWithMonitoring(key, data) {
  const dataSize = JSON.stringify(data).length;
  
  if (monitor.isNearLimit()) {
    console.warn('Storage near limit');
    if (!confirm('This may exceed storage limit. Continue?')) {
      return false;
    }
  }
  
  localStorage.setItem(key, JSON.stringify(data));
  monitor.usage = monitor.calculateUsage();
  
  return true;
}

Browser Compatibility

Understanding localStorage support across different browsers.

Feature Detection

// Comprehensive feature detection
function getStorageInfo() {
  const info = {
    localStorage: false,
    sessionStorage: false,
    quota: null,
    available: false
  };
  
  // Test localStorage
  try {
    localStorage.setItem('test', 'test');
    localStorage.removeItem('test');
    info.localStorage = true;
    info.available = true;
  } catch (e) {
    console.error('localStorage not available:', e);
  }
  
  // Test sessionStorage
  try {
    sessionStorage.setItem('test', 'test');
    sessionStorage.removeItem('test');
    info.sessionStorage = true;
  } catch (e) {
    console.error('sessionStorage not available:', e);
  }
  
  // Try to get quota information
  if ('storage' in navigator && 'estimate' in navigator.storage) {
    navigator.storage.estimate().then(estimate => {
      info.quota = estimate;
      console.log('Storage quota:', estimate);
    });
  }
  
  return info;
}

// Browser-specific workarounds
function getBestStorage() {
  // Safari private browsing
  const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
  const isPrivate = false;
  
  if (isSafari) {
    try {
      localStorage.setItem('test', 'test');
      localStorage.removeItem('test');
    } catch (e) {
      // Safari private mode
      return createMemoryStorage();
    }
  }
  
  // IE8 and below
  if (!window.localStorage) {
    // Use userData behavior for IE
    return createIEStorage();
  }
  
  return localStorage;
}

// IE userData fallback
function createIEStorage() {
  try {
    const storage = document.createElement('div');
    storage.style.display = 'none';
    storage.addBehavior('#default#userData');
    document.body.appendChild(storage);
    
    const storageObj = {
      setItem: function(key, value) {
        storage.load('store');
        storage.setAttribute(key, value);
        storage.save('store');
      },
      getItem: function(key) {
        storage.load('store');
        return storage.getAttribute(key);
      },
      removeItem: function(key) {
        storage.load('store');
        storage.removeAttribute(key);
        storage.save('store');
      }
    };
    
    return storageObj;
  } catch (e) {
    return null;
  }
}

Polyfills and Fallbacks

// localStorage polyfill for older browsers
(function() {
  if (typeof window.localStorage === 'undefined') {
    window.localStorage = {
      _data: {},
      
      setItem: function(id, val) {
        this._data[id] = String(val);
      },
      
      getItem: function(id) {
        return this._data.hasOwnProperty(id) ? this._data[id] : null;
      },
      
      removeItem: function(id) {
        return delete this._data[id];
      },
      
      clear: function() {
        this._data = {};
      },
      
      get length() {
        return Object.keys(this._data).length;
      },
      
      key: function(i) {
        const keys = Object.keys(this._data);
        return i < keys.length ? keys[i] : null;
      }
    };
  }
})();

// Enhanced localStorage with expiration
class StorageWithExpiration {
  constructor() {
    this.prefix = 'exp_';
  }
  
  setItem(key, value, ttl) {
    const item = {
      value: value,
      expires: ttl ? Date.now() + ttl : null
    };
    
    localStorage.setItem(this.prefix + key, JSON.stringify(item));
  }
  
  getItem(key) {
    const item = JSON.parse(localStorage.getItem(this.prefix + key));
    
    if (!item) return null;
    
    if (item.expires && Date.now() > item.expires) {
      localStorage.removeItem(this.prefix + key);
      return null;
    }
    
    return item.value;
  }
  
  removeItem(key) {
    localStorage.removeItem(this.prefix + key);
  }
}

// Usage
const expStorage = new StorageWithExpiration();
expStorage.setItem('temp_data', { id: 123 }, 60000); // Expires in 1 minute
const data = expStorage.getItem('temp_data');

Best Practices

Guidelines for using localStorage effectively and safely.

Data Management

// Use consistent key naming
const STORAGE_KEYS = {
  USER_PREFERENCES: 'user_prefs',
  AUTH_TOKEN: 'auth_token',
  THEME: 'ui_theme',
  LANGUAGE: 'ui_language',
  CART: 'shopping_cart'
};

// Create storage wrapper
class AppStorage {
  constructor() {
    this.prefix = 'myapp_';
  }
  
  key(name) {
    return this.prefix + STORAGE_KEYS[name];
  }
  
  set(key, value) {
    try {
      localStorage.setItem(this.key(key), JSON.stringify(value));
      return true;
    } catch (error) {
      console.error(`Storage error for ${key}:`, error);
      return false;
    }
  }
  
  get(key, defaultValue = null) {
    try {
      const item = localStorage.getItem(this.key(key));
      return item ? JSON.parse(item) : defaultValue;
    } catch (error) {
      console.error(`Storage parse error for ${key}:`, error);
      return defaultValue;
    }
  }
  
  remove(key) {
    localStorage.removeItem(this.key(key));
  }
  
  clear() {
    Object.values(STORAGE_KEYS).forEach(key => {
      localStorage.removeItem(this.key(key));
    });
  }
}

// Usage
const storage = new AppStorage();
storage.set('USER_PREFERENCES', { theme: 'dark' });
const prefs = storage.get('USER_PREFERENCES', {});

Error Handling and Validation

// Always validate data on retrieval
function validateAndRetrieve(key, validator, defaultValue = null) {
  try {
    const data = JSON.parse(localStorage.getItem(key));
    
    if (validator(data)) {
      return data;
    } else {
      console.warn(`Invalid data for ${key}, using default`);
      localStorage.removeItem(key);
      return defaultValue;
    }
  } catch (error) {
    console.error(`Error retrieving ${key}:`, error);
    return defaultValue;
  }
}

// Example validators
const validators = {
  user: (data) => {
    return data && 
           typeof data.id === 'number' && 
           typeof data.name === 'string' &&
           data.name.length > 0;
  },
  
  preferences: (data) => {
    return data && 
           typeof data === 'object' &&
           typeof data.theme === 'string';
  }
};

// Usage
const user = validateAndRetrieve('user', validators.user, { id: 0, name: 'Guest' });

// Handle storage events properly
window.addEventListener('storage', function(event) {
  // Ignore changes from current tab
  if (event.storageArea !== 'localStorage') return;
  
  // Handle specific keys
  switch (event.key) {
    case 'myapp_theme':
      updateTheme(event.newValue);
      break;
    case 'myapp_language':
      updateLanguage(event.newValue);
      break;
  }
});

// Clean up on logout
function secureCleanup() {
  // Remove authentication data
  storage.remove('AUTH_TOKEN');
  storage.remove('USER_SESSION');
  
  // Keep non-sensitive data
  // storage.remove('USER_PREFERENCES');
  
  // Clear any temporary data
  Object.keys(localStorage).forEach(key => {
    if (key.startsWith('temp_')) {
      localStorage.removeItem(key);
    }
  });
}

Summary

Key Takeaways

  • localStorage provides persistent key-value storage
  • Data is stored as strings and requires serialization
  • Same-origin policy restricts access to same domain
  • Storage has size limits (typically 5-10MB)
  • Storage events enable cross-tab communication

Best Practices

  • Always validate and sanitize stored data
  • Use consistent key naming conventions
  • Handle storage quota exceeded errors
  • Never store sensitive information like passwords
  • Provide fallbacks for when localStorage is unavailable

Common Pitfalls

  • Storing sensitive data in plaintext
  • Not handling storage quota exceeded errors
  • Forgetting localStorage is synchronous (blocking)
  • Not checking availability before use
  • Storing data without proper validation