logo

Nurav UI

Community
Hooks

useLocalStorage

A robust React hook for persisting state in window.localStorage with SSR safety and cross-tab synchronization.Copy Markdown

Overview

useLocalStorage acts like useState, but it automatically syncs the value with the browser's localStorage. This means data persists even after the page is refreshed or the browser is closed.

Key Features:

  • SSR Safe: Prevents errors during server-side rendering (Next.js/Vite environment).
  • Cross-Tab Syncing: Automatically updates state when changes occur in other browser tabs/windows.
  • Type Safe: Uses TypeScript generics to ensure your data remains correctly typed.
  • Functional Updates: Supports passing an updater function (prev => newVal).

Installation

Automatic (CLI)

Terminal
npx shadcn@latest add @nurav-ui/use-local-storage

Alternatively, install via direct URL:

Terminal
npx shadcn@latest add https://nurav-ui.vercel.app/r/use-local-storage.json

Manual Setup

Create the hook file

Copy the source into hooks/use-local-storage.ts:

hooks/use-local-storage.ts
import { useState, useCallback, useEffect } from "react";

export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void] {
  const readValue = useCallback((): T => {
    if (typeof window === "undefined") return initialValue;
    try {
      const item = window.localStorage.getItem(key);
      return item ? (JSON.parse(item) as T) : initialValue;
    } catch (error) {
      console.warn(`Error reading localStorage key “${key}”:`, error);
      return initialValue;
    }
  }, [key, initialValue]);

  const [storedValue, setStoredValue] = useState<T>(readValue);

  const setValue = useCallback((value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      if (typeof window !== "undefined") {
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
        window.dispatchEvent(new Event("local-storage"));
      }
    } catch (error) {
      console.warn(`Error setting localStorage key “${key}”:`, error);
    }
  }, [key, storedValue]);

  useEffect(() => {
    const handleStorageChange = (event: StorageEvent) => {
      if (event.key === key) setStoredValue(readValue());
    };
    const handleCustomEvent = () => setStoredValue(readValue());

    window.addEventListener("storage", handleStorageChange);
    window.addEventListener("local-storage" as any, handleCustomEvent);
    return () => {
      window.removeEventListener("storage", handleStorageChange);
      window.removeEventListener("local-storage" as any, handleCustomEvent);
    };
  }, [key, readValue]);

  return [storedValue, setValue];
}

Usage

Theme Toggler Example

Persisting user theme preference across sessions.

import { useLocalStorage } from '@/hooks/use-local-storage';

export default function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage('app-theme', 'light');

  const toggleTheme = () => {
    setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
  };

  return (
    <div className={`p-8 ${theme === 'dark' ? 'bg-black text-white' : 'bg-white text-black'}`}>
      <p>Current Theme: {theme}</p>
      <button onClick={toggleTheme} className="border p-2 rounded">
        Change to {theme === 'light' ? 'Dark' : 'Light'}
      </button>
    </div>
  );
}

Shopping Cart Example

Keeping items in the cart even after a page refresh.

import { useLocalStorage } from '@/hooks/use-local-storage';

export default function MiniCart() {
  const [cartCount, setCartCount] = useLocalStorage('cart-count', 0);

  return (
    <div className="flex items-center gap-4 p-4 border rounded-lg">
      <span>Items in Cart: {cartCount}</span>
      <button 
        onClick={() => setCartCount(c => c + 1)}
        className="px-4 py-2 bg-primary text-primary-foreground rounded-md"
      >
        Add to Cart
      </button>
      <button onClick={() => setCartCount(0)} className="text-destructive">
        Clear
      </button>
    </div>
  );
}

API Reference

Parameters

PropTypeDefaultDescription
keyrequired
stringThe key used in `window.localStorage`.
initialValuerequired
TThe default value if the key doesn't exist in storage yet.

Return Value

PropTypeDefaultDescription
storedValue
TThe current state value.
setValue
FunctionFunction to update the value (and the storage). Supports functional updates.

Storage Limits: Remember that localStorage has a size limit of roughly 5MB per origin. Avoid storing massive binary data or base64 images here.

Cross-Tab Syncing: Try opening this page in two separate windows. Updating the theme in one window will instantly update the theme in the other without a refresh!

Built by Varun

Last Updated:April 10, 2026

On this page