DEV Community

Muhammed Fayaz T S
Muhammed Fayaz T S

Posted on

useSyncExternalStore: The Right Way to Sync React with localStorage

React 18 introduced a low-level hook that most developers never touch directly, but almost every modern state library depends on:

useSyncExternalStore

It is React’s official way to connect components to external state sources — state that lives outside React.

Examples:

  • localStorage
  • Redux / Zustand stores
  • browser APIs
  • WebSocket data

What problem does it solve?

Before React 18, we usually subscribed to stores like this:

useEffect(() => store.subscribe(forceUpdate), []);
Enter fullscreen mode Exit fullscreen mode

This breaks under concurrent rendering.

useSyncExternalStore fixes this by letting React control how it:

  • subscribes to changes
  • reads the current value
  • triggers re-renders safely

The API

const value = useSyncExternalStore(
  subscribe,
  getSnapshot,
  getServerSnapshot?
);
Enter fullscreen mode Exit fullscreen mode
  • subscribe → how React listens for updates
  • getSnapshot → how React reads current state

When the snapshot changes, React re-renders.


Example: persistent state with localStorage

1. Create a tiny external store

function createLocalStorageStore(key, initialValue) {
  const listeners = new Set();

  const getSnapshot = () => {
    const data = localStorage.getItem(key);
    return data ? JSON.parse(data) : initialValue;
  };

  const setValue = (value) => {
    localStorage.setItem(key, JSON.stringify(value));
    listeners.forEach((l) => l());
  };

  const subscribe = (listener) => {
    listeners.add(listener);

    const onStorage = (e) => {
      if (e.key === key) listener();
    };

    window.addEventListener("storage", onStorage);

    return () => {
      listeners.delete(listener);
      window.removeEventListener("storage", onStorage);
    };
  };

  return { getSnapshot, setValue, subscribe };
}
Enter fullscreen mode Exit fullscreen mode

2. Connect it to React

import { useSyncExternalStore } from "react";

function useExternalStore(store) {
  return useSyncExternalStore(
    store.subscribe,
    store.getSnapshot,
    store.getSnapshot
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Use it

const counterStore = createLocalStorageStore("counter", 0);

function Counter() {
  const count = useExternalStore(counterStore);

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => counterStore.setValue(count + 1)}>
        +
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now your state is:

  • persistent
  • shared
  • cross-tab synced
  • concurrent-safe

When should you use this?

Use useSyncExternalStore when:

  • state lives outside React
  • many components share the same data
  • you’re building a store, persistence layer, or library

For normal component state, useState is still the right tool.


Final takeaway

useState manages React-owned state.
useSyncExternalStore connects React to the outside world.

If you’re building anything beyond local component state, this hook is the foundation.

Top comments (1)

Collapse
 
deiga profile image
Timo Sand

Does this really work? The "storage" event is supposedly only sent to other open Tabs and not the currently open one.