DEV Community

Ashish Patel
Ashish Patel

Posted on

How useSyncExternalStore Transformed My React State Management


As a React developer, I’ve always been conscious of how I manage state, especially when integrating with external data sources or global stores. Over the years, I’ve used different patterns—useEffect hooks, custom subscriptions, and third-party libraries like Redux or Zustand. But recently, while working on a React 18+ project, I discovered useSyncExternalStore, and it genuinely changed my perspective on syncing external state with React components.


My Initial Pain Points with External Store Subscriptions

In one project, I needed to build a real-time dashboard listening to data from a custom in-memory store. Initially, I wrote the subscription logic using useEffect and useState. However, I ran into tricky bugs—sometimes the UI wouldn’t reflect the latest state correctly during fast updates, and in SSR scenarios, the server-rendered content didn’t always match the hydrated client UI.

The problem was subtle but frustrating. React’s concurrent rendering means components can render multiple times before committing, and the old subscription patterns weren’t designed for that.


Discovering useSyncExternalStore

When I came across React’s useSyncExternalStore hook, it was a lightbulb moment. This hook is created specifically to handle subscriptions to external “stores” safely with React 18’s concurrent features and supports server-side rendering.

It allows React to safely read a snapshot of external state and subscribe to updates, so UI stays consistent without race conditions or hydration mismatches.


How I Used It in My Project

I refactored my custom store and React component to use useSyncExternalStore like this:

// My custom store
let count = 0;
const listeners = new Set();

export const counterStore = {
  getSnapshot() {
    return count;
  },
  subscribe(callback) {
    listeners.add(callback);
    return () => listeners.delete(callback);
  },
  increment() {
    count++;
    listeners.forEach(listener => listener());
  },
};
Enter fullscreen mode Exit fullscreen mode

And in React:

import React, { useSyncExternalStore } from "react";
import { counterStore } from "./counterStore";

function Counter() {
  const count = useSyncExternalStore(
    counterStore.subscribe,
    counterStore.getSnapshot,
    counterStore.getSnapshot // SSR fallback
  );

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={counterStore.increment}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This switch made UI updates flawless—no more stale renders or mismatches. Updates were consistent and aligned perfectly with React 18’s rendering lifecycle.


Why This Matters for React Developers

Before this, I was tempted to keep using familiar patterns with useEffect for subscriptions, but these do not future-proof apps for the latest React features. SSR was especially complicated without snapshot fallbacks.

useSyncExternalStore solves these by:

  • Providing React the current snapshot during render.
  • Registering subscriptions safe for concurrent renders.
  • Offering a server snapshot for consistent SSR.

Other Use Cases I Tried

I also used useSyncExternalStore to track window width via a custom hook. It replaced my old useEffect based event listener pattern and handled SSR with a sensible default width fallback.

function useWindowWidth() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener("resize", callback);
      return () => window.removeEventListener("resize", callback);
    },
    () => window.innerWidth,
    () => 1024 // default width for SSR
  );
}
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

Switching to useSyncExternalStore was a small change with a big impact on the reliability and maintainability of my React apps, especially with concurrent features and SSR becoming the norm.

If managing external subscriptions or custom global state today, I highly recommend exploring this hook. It’s already powering popular state libraries under the hood, so take advantage of it directly!

Hope my experience helps others avoid the pitfalls I ran into and build more resilient React apps.


Would love to hear how others are using this hook or challenges faced!


Top comments (0)