← Back to snippets
typescriptreacthooksstoragestate

useLocalStorage Hook

React hook for persistent state with localStorage. Includes SSR safety and type inference.

Code

import { useState, useEffect } from 'react';

function useLocalStorage<T>(key: string, initialValue: T) {
  // Get from localStorage or use initial value
  const [storedValue, setStoredValue] = useState<T>(() => {
    if (typeof window === 'undefined') return initialValue;

    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  // Update localStorage when value changes
  useEffect(() => {
    if (typeof window === 'undefined') return;

    try {
      window.localStorage.setItem(key, JSON.stringify(storedValue));
    } catch (error) {
      console.warn('localStorage write failed:', error);
    }
  }, [key, storedValue]);

  return [storedValue, setStoredValue] as const;
}

// Usage
const [theme, setTheme] = useLocalStorage('theme', 'dark');

Variations

With expiration

function useLocalStorageWithExpiry<T>(key: string, initialValue: T, ttlMs: number) {
  const [value, setValue] = useState<T>(() => {
    if (typeof window === 'undefined') return initialValue;
    const item = localStorage.getItem(key);
    if (!item) return initialValue;
    const { value, expiry } = JSON.parse(item);
    return Date.now() > expiry ? initialValue : value;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify({
      value,
      expiry: Date.now() + ttlMs
    }));
  }, [key, value, ttlMs]);

  return [value, setValue] as const;
}