useSyncExternalStore: The Right Way to Sync React with localStorage

Published: (January 8, 2026 at 11:00 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

Introduction

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, such as:

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

Why useSyncExternalStore?

Before React 18, subscribing to external stores was often done like this:

useEffect(() => store.subscribe(forceUpdate), []);

This pattern breaks under concurrent rendering. useSyncExternalStore fixes the problem by letting React control:

  • Subscription to changes
  • Reading the current value
  • Triggering re‑renders safely
const value = useSyncExternalStore(
  subscribe,
  getSnapshot,
  getServerSnapshot? // optional, for server‑side rendering
);
  • subscribe – how React listens for updates
  • getSnapshot – how React reads the current state

When the snapshot changes, React re‑renders automatically.

Creating a localStorage 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 };
}

Hook wrapper for external stores

import { useSyncExternalStore } from "react";

function useExternalStore(store) {
  return useSyncExternalStore(
    store.subscribe,
    store.getSnapshot,
    store.getSnapshot // same function for server snapshot if needed
  );
}

Example: Counter synced with localStorage

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

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

  return (
    <>
      Count: {count}
      {/* The original snippet omitted the button element; kept as‑is */}
      counterStore.setValue(count + 1)}>+
    </>
  );
}

Now the counter state is:

  • Persistent (saved in localStorage)
  • Shared across components
  • Cross‑tab synced via the storage event
  • Concurrent‑safe thanks to useSyncExternalStore

When to use useSyncExternalStore

  • The state lives outside React (e.g., browser storage, external stores, WebSocket data).
  • Many components need to share the same data.
  • You’re building a store, persistence layer, or library that other components will consume.

For normal component‑local state, continue using useState. useState manages React‑owned state, while useSyncExternalStore connects React to the outside world. If you’re building anything beyond local component state, this hook is the foundation.

Back to Blog

Related posts

Read more »