useLocalStorage Custom Hook

Overview

The useLocalStorage hook is a custom hook that extends React's useState to persist state in the browser's localStorage. This allows data to persist between page refreshes and browser sessions, providing a seamless user experience.

Example 1: Text Input Persistence

Regular useState (not persisted)

This input uses regular useState. Its value will be lost on page refresh.

useLocalStorage Hook (persisted)

This input uses our custom useLocalStorage hook. Try refreshing the page - the value will persist!

Example 2: Theme Preference

Dark Mode Toggle (persisted)

Current theme: Light

This example uses the same useLocalStorage hook but with a boolean value. Refresh the page and your theme preference will be remembered.

Implementation

// useLocalStorage.tsx
export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void] {
  // Get value from localStorage or use initial value
  const readValue = (): T => {
    if (typeof window === 'undefined') {
      return initialValue;
    }
    
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.warn(`Error reading localStorage key "${key}":`, error);
      return initialValue;
    }
  };
  
  // State to store our value
  const [storedValue, setStoredValue] = useState<T>(readValue);
  
  // Return a wrapped version of useState's setter function that persists the new value to localStorage
  const setValue = (value: T | ((val: T) => T)) => {
    try {
      // Allow value to be a function so we have same API as useState
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      
      // Save state
      setStoredValue(valueToStore);
      
      // Save to localStorage
      if (typeof window !== 'undefined') {
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      console.warn(`Error setting localStorage key "${key}":`, error);
    }
  };
  
  return [storedValue, setValue];
}

Key Points

  • Uses TypeScript generics to handle various data types
  • Handles edge cases like server-side rendering
  • Maintains the same API as React's useState
  • Handles errors with try/catch blocks
  • Supports value initialization from function

Usage Pattern

// Using the hook in a component
const MyComponent = () => {
  // String example
  const [name, setName] = useLocalStorage<string>('user-name', '');
  
  // Object example
  const [user, setUser] = useLocalStorage<{ id: number, name: string }>(
    'user-data', 
    { id: 0, name: '' }
  );
  
  // Boolean example
  const [isLoggedIn, setIsLoggedIn] = useLocalStorage<boolean>('logged-in', false);

  return (
    <div>
      {/* Component content */}
    </div>
  );
};