Skip to main content

useLocalStorage Deep Dive

The useLocalStorage hook provides a reactive interface for interacting with the browser's localStorage API, ensuring synchronization across different components and even different tabs.


Core Concepts

The hook follows a pattern similar to React's useState, but persists the value in the browser's storage.

import { useLocalStorage } from '@archibald/storefront';

function MyComponent() {
const [name, setName, clearName] = useLocalStorage('user-name');

return (
<div>
<p>Stored Name: {name}</p>
<input onChange={(e) => setName(e.target.value)} />
<button onClick={clearName}>Clear</button>
</div>
);
}

Reactivity & Synchronization

Unlike direct localStorage.getItem() calls, useLocalStorage is reactive. It uses useSyncExternalStore to subscribe to storage changes.

  • Multi-Component Sync: If two components use the same key, updating the value in one will automatically trigger a re-render in the other.
  • Cross-Tab Sync: The hook listens for the native browser storage event. If a user updates the value in a different tab, the hook will detect the change and update the UI in the current tab.

SSR Compatibility

useLocalStorage is designed to be safe for Server-Side Rendering.

  • Server-Side: Since localStorage is a browser-only API, the hook returns an empty string ('') on the server.
  • Hydration: The value is retrieved from storage only once the component hydrates in the browser, preventing hydration mismatches.

How it Works Step-by-Step

  1. Subscription: Upon mounting in the browser, the hook adds an event listener for the native storage event.
  2. Initial Read: It calls LocalStorageHelper.get(key) to retrieve the current value.
  3. State Management: It uses useSyncExternalStore to manage the local state, ensuring it stays in sync with the external "source of truth" (localStorage).
  4. Updating: When set(value) is called:
    • It uses LocalStorageHelper.set(key, value) to update the actual browser storage.
    • It dispatches a manual StorageEvent. This is necessary because the native storage event only fires for changes made in other windows/tabs. Dispatching it manually ensures components in the current window also update.
  5. Clearing: When clear() is called, it deletes the key and dispatches a storage event with an empty value.
  6. Cleanup: When the component unmounts, the event listener is removed to prevent memory leaks.

Best Practice: Use useLocalStorage for non-sensitive user preferences (e.g., theme choice, dismissed banners) rather than sensitive data or large state objects.