DEV Community

Saiful Islam
Saiful Islam

Posted on

useSyncExternalStore in React — The Right Way to Subscribe to External Data

Starting with React 18, a new hook called useSyncExternalStore was introduced.
At first glance, it might look confusing — why do we need it when we already have useState, useEffect, or useRef?

The answer lies in React’s new concurrent rendering model.

When your component depends on data from an external source (like browser events, a global store, or even a WebSocket connection), React needs a way to read that data consistently — even during complex renders or Suspense transitions.

That’s where useSyncExternalStore comes in.


⚙️ What is useSyncExternalStore?

useSyncExternalStore is a low-level React hook designed to let components safely subscribe to external data sources.

It ensures:

  • React always reads a consistent snapshot of the external state.
  • Avoids tearing — a bug where different components read inconsistent versions of external state.
  • Works correctly with Concurrent Mode and Server Components.

🧩 The Hook Signature

const snapshot = useSyncExternalStore(
  subscribe,
  getSnapshot,
  getServerSnapshot?
);
Enter fullscreen mode Exit fullscreen mode

Here’s what each argument means:

  • subscribe(callback)
    A function that registers a listener. React will call this callback whenever the external data changes.

  • getSnapshot()
    Returns the current value of the external data.

  • getServerSnapshot()
    (Optional) Returns a fallback snapshot during server-side rendering (SSR).

React uses getSnapshot() during render, and re-renders the component if subscribe() notifies it that something changed.


🧠 Why Not Just Use useEffect?

Before React 18, we used something like this:

const [isOnline, setIsOnline] = useState(navigator.onLine);

useEffect(() => {
  function updateStatus() {
    setIsOnline(navigator.onLine);
  }

  window.addEventListener("online", updateStatus);
  window.addEventListener("offline", updateStatus);

  return () => {
    window.removeEventListener("online", updateStatus);
    window.removeEventListener("offline", updateStatus);
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

This works fine in normal rendering, but can fail in concurrent scenarios (e.g., during Suspense, streaming SSR, or concurrent updates).
React might read outdated data or re-render unnecessarily.
useSyncExternalStore fixes this problem by providing synchronised reads of external state.


💻 Example: Tracking Online/Offline Status

Let’s create a reusable hook using useSyncExternalStore:

import { useSyncExternalStore } from "react";

// 1️⃣ Subscribe to browser online/offline events
function subscribe(callback: () => void) {
  window.addEventListener("online", callback);
  window.addEventListener("offline", callback);

  return () => {
    window.removeEventListener("online", callback);
    window.removeEventListener("offline", callback);
  };
}

// 2️⃣ Snapshot getter
function getSnapshot() {
  return navigator.onLine;
}

// 3️⃣ Optional SSR fallback
function getServerSnapshot() {
  return true; // assume online on the server
}

// 4️⃣ Our custom hook
export function useOnlineStatus() {
  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
}
Enter fullscreen mode Exit fullscreen mode

Now we can use this hook anywhere:

import React from "react";
import { useOnlineStatus } from "./useOnlineStatus";

export default function NetworkStatus() {
  const isOnline = useOnlineStatus();

  return (
    <div
      style={{
        padding: "2rem",
        textAlign: "center",
        fontFamily: "sans-serif",
      }}
    >
      <h1>
        You are currently:{" "}
        <span style={{ color: isOnline ? "green" : "red" }}>
          {isOnline ? "Online" : "Offline"}
        </span>
      </h1>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

🧩 How It Works

  1. subscribe() tells React how to listen for external changes. When an event happens, React calls the stored callback.
  2. getSnapshot() reads the current external value. React calls it whenever it needs the latest state.
  3. When the external source changes, React re-renders the component with the latest snapshot.
  4. No risk of race conditions or stale state, even in concurrent rendering.

⚡ Other Use Cases

useSyncExternalStore can be used for:

  • Subscribing to a custom store (like Zustand or Redux)
  • Watching WebSocketupdates
  • Reacting to localStorage/sessionStorage changes
  • Integrating with browser APIs (media queries, geolocation, etc.)

Example with localStorage:

function subscribe(callback: () => void) {
  window.addEventListener("storage", callback);
  return () => window.removeEventListener("storage", callback);
}

function getSnapshot() {
  return localStorage.getItem("theme") || "light";
}

export const useThemeStore = () =>
  useSyncExternalStore(subscribe, getSnapshot, () => "light");
Enter fullscreen mode Exit fullscreen mode

🧭 Key Takeaways

useSyncExternalStore lets React safely read external data.
✅ Prevents stale state issues in concurrent rendering.
✅ Perfect for browser APIs, custom stores, and global events.
✅ Always use it when your component reads state outside React.


🧩 Conclusion

useSyncExternalStore might seem complex at first, but it’s the correct way to subscribe to data outside React’s control — whether that’s browser state, global stores, or server signals.

By using it, your components stay predictable, synchronised, and ready for React’s future.

Top comments (0)