Join our Discord Community

useReadLocalStorage

React hook that reads a value from localStorage with automatic updates, cross-tab synchronization, and SSR compatibility.

Loading component...

Installation

npx shadcn@latest add https://www.shadcn.io/registry/use-read-local-storage.json
npx shadcn@latest add https://www.shadcn.io/registry/use-read-local-storage.json
pnpm dlx shadcn@latest add https://www.shadcn.io/registry/use-read-local-storage.json
bunx shadcn@latest add https://www.shadcn.io/registry/use-read-local-storage.json

Features

  • Read-only access - Lightweight alternative to useLocalStorage for read-only scenarios
  • Automatic updates - Responds to localStorage changes across tabs and windows
  • SSR compatible - Optional initialization for server-side rendering
  • Custom deserialization - Support for custom deserializer functions
  • Type safety - Full TypeScript support with proper generics
  • Error handling - Graceful fallbacks for localStorage errors
  • Cross-tab sync - Updates when localStorage changes in other tabs

Usage

import { useReadLocalStorage } from "@/hooks/use-read-local-storage"

function UserProfile() {
  // Assuming a value was set in localStorage with this key
  const userPreferences = useReadLocalStorage<{ theme: string; lang: string }>("user-prefs")

  return (
    <div>
      {userPreferences && (
        <>
          <p>Theme: {userPreferences.theme}</p>
          <p>Language: {userPreferences.lang}</p>
        </>
      )}
    </div>
  )
}

API Reference

useReadLocalStorage

// Client-side version
useReadLocalStorage<T>(key: string, options?: Partial<Options<T, true>>): T | null

// SSR version
useReadLocalStorage<T>(key: string, options: Options<T, false>): T | null | undefined

Options

PropertyTypeDefaultDescription
deserializer(value: string) => TJSON.parseCustom function to deserialize the stored string value
initializeWithValuebooleantrueWhether to initialize with localStorage value (set to false for SSR)

Return Value

  • T | null - The stored value or null if key doesn't exist or error occurs
  • T | null | undefined - When initializeWithValue: false, may return undefined during SSR

Usage Examples

Basic Reading

const darkMode = useReadLocalStorage<boolean>("darkMode")
const username = useReadLocalStorage<string>("username")
const settings = useReadLocalStorage<{ notifications: boolean }>("settings")

return (
  <div className={darkMode ? "dark" : "light"}>
    <p>Welcome, {username || "Guest"}</p>
    <p>Notifications: {settings?.notifications ? "On" : "Off"}</p>
  </div>
)

Custom Deserializer

const lastVisit = useReadLocalStorage<Date>("lastVisit", {
  deserializer: (value) => new Date(value)
})

return (
  <div>
    {lastVisit && <p>Last visit: {lastVisit.toLocaleDateString()}</p>}
  </div>
)

SSR Compatible

const theme = useReadLocalStorage<string>("theme", {
  initializeWithValue: false
})

// During SSR, theme will be undefined
// After hydration, theme will be the stored value or null

return (
  <div className={theme || "default-theme"}>
    Content
  </div>
)

Reading User Preferences

function AppSettings() {
  const userSettings = useReadLocalStorage<{
    theme: "light" | "dark"
    language: string
    notifications: boolean
    autoSave: boolean
  }>("app-settings")

  if (!userSettings) {
    return <div>Loading user settings...</div>
  }

  return (
    <div>
      <h2>Current Settings</h2>
      <ul>
        <li>Theme: {userSettings.theme}</li>
        <li>Language: {userSettings.language}</li>
        <li>Notifications: {userSettings.notifications ? "Enabled" : "Disabled"}</li>
        <li>Auto-save: {userSettings.autoSave ? "On" : "Off"}</li>
      </ul>
    </div>
  )
}

Watching Shopping Cart

function CartIndicator() {
  const cartItems = useReadLocalStorage<Array<{ id: string; name: string; qty: number }>>("cart-items")
  
  const totalItems = cartItems?.reduce((sum, item) => sum + item.qty, 0) || 0

  return (
    <button>
      🛒 Cart ({totalItems})
    </button>
  )
}

Reading Authentication Token

function AuthStatus() {
  const token = useReadLocalStorage<string>("auth-token")
  const user = useReadLocalStorage<{ name: string; email: string }>("user-data")

  return (
    <div>
      {token ? (
        <div>
          <p>✅ Logged in as {user?.name}</p>
          <p>Email: {user?.email}</p>
        </div>
      ) : (
        <p>❌ Not logged in</p>
      )}
    </div>
  )
}

Multiple Storage Keys

function Dashboard() {
  const theme = useReadLocalStorage<string>("theme")
  const layout = useReadLocalStorage<"grid" | "list">("dashboard-layout")  
  const filters = useReadLocalStorage<string[]>("active-filters")
  const lastSync = useReadLocalStorage<Date>("last-sync", {
    deserializer: (value) => new Date(value)
  })

  return (
    <div className={`theme-${theme || "default"}`}>
      <div className={layout === "grid" ? "grid-layout" : "list-layout"}>
        <p>Active filters: {filters?.join(", ") || "None"}</p>
        <p>Last sync: {lastSync?.toLocaleString() || "Never"}</p>
      </div>
    </div>
  )
}

Common Use Cases

  • Reading user preferences - Theme, language, layout settings
  • Authentication state - Reading stored tokens or user data
  • Shopping cart display - Showing cart item count from localStorage
  • Feature flags - Reading enabled/disabled features
  • Recently viewed items - Displaying browsing history
  • Form auto-save - Reading saved draft content
  • Analytics settings - Reading consent and tracking preferences
  • App configuration - Reading stored configuration options

Comparison with useLocalStorage

FeatureuseReadLocalStorageuseLocalStorage
Read values
Write values
Bundle sizeSmallerLarger
Use caseRead-only scenariosRead/write scenarios
PerformanceOptimized for readingGeneral purpose

Implementation Details

  • Uses useEventListener to listen for both storage and custom local-storage events
  • Automatically updates when localStorage changes in the current tab or other tabs
  • Uses JSON.parse by default but supports custom deserializer functions
  • Handles localStorage errors gracefully by returning null
  • Supports server-side rendering with initializeWithValue: false
  • Re-reads value whenever the key changes
  • Provides type safety with TypeScript generics

Best Practices

  1. Use for read-only scenarios: When you only need to read localStorage values
  2. Handle null values: Always check if the returned value is null
  3. Provide fallbacks: Use default values when localStorage is empty
  4. Custom deserializers: Use for complex data types like dates
  5. SSR considerations: Set initializeWithValue: false for server-side rendering
  6. Type safety: Always specify the generic type for better TypeScript support