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 (0)