Skillversitas

Web Storage API

The Web Storage API provides a way to store data in the user's browser: localStorage (persistent, doesn't disappear when the tab is closed) and sessionStorage (gone when the tab/session is closed). Both store data as string key-value pairs.

localStorage vs sessionStorage

Feature localStorage sessionStorage
Duration Permanent Until the tab is closed
Scope All tabs (same origin) Active tab only
Capacity ~5-10 MB ~5-10 MB
Access Sync API Sync API

Code Example

Basics — setItem, getItem, removeItem

// Store data
localStorage.setItem('nameUser', 'Mike Johnson');
localStorage.setItem('studentIdUser', '2024001');

// Retrieve data
const name = localStorage.getItem('nameUser');
console.log(name); // "Mike Johnson"

// Remove a single item
localStorage.removeItem('studentIdUser');

// Remove all items
localStorage.clear();

// Number of items
console.log(localStorage.length);

// Get a key by index
const key = localStorage.key(0);

Storing Objects/Arrays with JSON

// localStorage only stores strings
// So objects/arrays must be JSON.stringify'd first

const student = {
  studentId: '2024001',
  name: 'Mike Johnson',
  major: 'Computer Science',
  course: ['Algorithms', 'Database', 'Networks']
};

// Save
localStorage.setItem('student', JSON.stringify(student));

// Retrieve
const data = JSON.parse(localStorage.getItem('student'));
console.log(data.name); // "Mike Johnson"
console.log(data.course[0]); // "Algorithms"

// Safer helper functions
function saveToLocal(key, value) {
  try {
    localStorage.setItem(key, JSON.stringify(value));
    return true;
  } catch (error) {
    console.error('Failed to save:', error);
    return false;
  }
}

function getFromLocal(key, defaultValue = null) {
  try {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : defaultValue;
  } catch (error) {
    console.error('Read failed:', error);
    return defaultValue;
  }
}

Code Explanation

Storage Events

The storage event fires when a localStorage item is changed — but ONLY from another tab/browser (not the same tab).

// Listen for changes from other tabs
window.addEventListener('storage', function (event) {
  console.log('Key changed:', event.key);
  console.log('Grade old:', event.oldValue);
  console.log('Grade new:', event.newValue);
  console.log('URL page:', event.url);
});

// Useful for synchronization between tabs
// Example: user logs in on tab A → tab B updates automatically
window.addEventListener('storage', function (event) {
  if (event.key === 'authToken' && event.newValue === null) {
    // Token was removed from another tab — log out here too
    window.location.href = '/login';
  }
});

Error Handling

// localStorage can fill up — always handle the error
function safeSetItem(key, value) {
  try {
    localStorage.setItem(key, JSON.stringify(value));
    return true;
  } catch (error) {
    if (error.name === 'QuotaExceededError') {
      console.error('Storage isFull!');
      // Clean up old data or show a warning
      clearOldStorage();
    } else {
      console.error('Storage error:', error);
    }
    return false;
  }
}

function clearOldStorage() {
  // Remove non-critical data
  const keysToClean = ['cache', 'draftForm', 'recentSearches'];
  keysToClean.forEach(key => localStorage.removeItem(key));
}

Real-World Examples

Saving a Student Form Draft

const formStudent = document.querySelector('#form-student');
const STORAGE_KEY = 'draftStudent';

// Auto-save while typing
formStudent.querySelectorAll('input, select, textarea').forEach(field => {
  field.addEventListener('input', function () {
    saveDraft();
  });
});

// Save the draft to localStorage
function saveDraft() {
  const formData = new FormData(formStudent);
  const draft = Object.fromEntries(formData);
  draft.lastSaved = new Date().toISOString();
  localStorage.setItem(STORAGE_KEY, JSON.stringify(draft));
}

// Load the draft when the page opens
function loadDraft() {
  const saved = localStorage.getItem(STORAGE_KEY);
  if (!saved) return;

  const draft = JSON.parse(saved);
  const lastSaved = new Date(draft.lastSaved).toLocaleString('id-ID');

  const restore = confirm(
    `Found a draft from ${lastSaved}.\nUse this draft?`
  );

  if (restore) {
    Object.keys(draft).forEach(key => {
      if (key === 'lastSaved') return;
      const field = formStudent.querySelector(`[name="${key}"]`);
      if (field) field.value = draft[key];
    });
  } else {
    localStorage.removeItem(STORAGE_KEY);
  }
}

// Remove the draft after a successful submit
formStudent.addEventListener('submit', async function (e) {
  e.preventDefault();
  try {
    await submitForm(this);
    localStorage.removeItem(STORAGE_KEY);
    showNotification('Data tersave!', 'success');
  } catch (error) {
    console.error(error);
  }
});

// Load the draft when the page is ready
loadDraft();

Remembering User Preferences

class UserPreferences {
  constructor(storageKey = 'userPrefs') {
    this.storageKey = storageKey;
    this.defaults = {
      theme: 'light',
      language: 'id',
      fontSize: '14',
      sidebarOpen: true,
      notificationsEnabled: true
    };
  }

  get(key) {
    const prefs = this.getAll();
    return prefs[key] !== undefined ? prefs[key] : this.defaults[key];
  }

  set(key, value) {
    const prefs = this.getAll();
    prefs[key] = value;
    try {
      localStorage.setItem(this.storageKey, JSON.stringify(prefs));
      this.applyPreferences();
    } catch (error) {
      console.error('Failed to save preferences:', error);
    }
  }

  getAll() {
    try {
      const stored = localStorage.getItem(this.storageKey);
      return stored ? JSON.parse(stored) : { ...this.defaults };
    } catch {
      return { ...this.defaults };
    }
  }

  reset() {
    localStorage.removeItem(this.storageKey);
    this.applyPreferences();
  }

  applyPreferences() {
    const prefs = this.getAll();
    document.documentElement.setAttribute('data-theme', prefs.theme);
    document.documentElement.style.fontSize = prefs.fontSize + 'px';
    document.documentElement.lang = prefs.language;
  }
}

// Initialize
const prefs = new UserPreferences();
prefs.applyPreferences(); // apply when the page loads

// Toggle theme
document.querySelector('#theme-toggle').addEventListener('click', function () {
  const current = prefs.get('theme');
  prefs.set('theme', current === 'light' ? 'dark' : 'light');
});

// Save font size
document.querySelector('#font-size').addEventListener('change', function () {
  prefs.set('fontSize', this.value);
});

Saving Search History

class SearchHistory {
  constructor(maxItems = 10) {
    this.storageKey = 'searchHistory';
    this.maxItems = maxItems;
  }

  add(query) {
    let history = this.get();

    // Remove duplicates
    history = history.filter(item => item.query !== query);

    // Add to the front
    history.unshift({
      query,
      timestamp: new Date().toISOString()
    });

    // Limit the count
    history = history.slice(0, this.maxItems);

    localStorage.setItem(this.storageKey, JSON.stringify(history));
  }

  get() {
    try {
      return JSON.parse(localStorage.getItem(this.storageKey)) || [];
    } catch {
      return [];
    }
  }

  clear() {
    localStorage.removeItem(this.storageKey);
  }
}

// Usage
const searchHistory = new SearchHistory();

// Search input
searchInput.addEventListener('keyup', function (e) {
  if (e.key === 'Enter' && this.value.trim()) {
    searchHistory.add(this.value.trim());
    performSearch(this.value.trim());
  }
});

// Show the history when the input is focused
searchInput.addEventListener('focus', function () {
  const history = searchHistory.get();
  if (history.length === 0) return;

  const dropdown = document.createElement('div');
  dropdown.className = 'search-dropdown';
  dropdown.innerHTML = history.map(item => `
    <div class="search-item" data-query="${item.query}">
      ${item.query}
    </div>
  `).join('');

  searchInput.parentNode.appendChild(dropdown);
});

sessionStorage — Example

// SessionStorage — gone when the tab is closed
// Suited for temporary data within a single session

sessionStorage.setItem('currentStep', '1');
sessionStorage.getItem('currentStep'); // "1"

// Multi-step form — save progress per step
function saveCurrentStep(step, data) {
  sessionStorage.setItem(`step${step}`, JSON.stringify(data));
}

function getStepData(step) {
  return JSON.parse(sessionStorage.getItem(`step${step}`) || 'null');
}

// Student wizard form
const currentStep = parseInt(sessionStorage.getItem('currentStep') || '1');

// Step 1: Personal Data → Step 2: Choose Major → Step 3: Confirm
if (currentStep > 1) {
  const prevData = getStepData(currentStep - 1);
  if (!prevData) {
    // No data from the previous step — go back to step 1
    window.location.href = '?step=1';
  }
}

Conclusion

The Web Storage API stores data in the browser without needing a server. Use localStorage for persistent data (preferences, drafts), and sessionStorage for session data (form progress, wizard steps). Always use try/catch to handle QuotaExceededError, and helper functions like saveToLocal/getFromLocal for safer code. Use JSON.stringify/parse to store objects or arrays.