Now Reading:

Leveraging useSyncExternalStore to Build Smarter React Components

Let’s say you’ve got a global store like Redux, Zustand, and you want your components to re-render when that store updates. Classic solution? You subscribe to changes. But React 18 introduced a hook that makes this pattern safer and more efficient- useSyncExternalStore to the rescue!

Why useSyncExternalStore Exists

#

It’s common that you’ll often see patterns like this:


_10
useEffect(() => {
_10
const unsubscribe = store.subscribe(() => forceUpdate());
_10
return unsubscribe;
_10
}, []);

This worked… most of the time. But React's new concurrent rendering model introduced some subtle issues. With concurrent rendering, your component could read outdated values between renders.

This also means React’s memo and useMemo wouldn’t work properly if external data changed without React knowing.

So the React team introduced useSyncExternalStore—a hook built specifically to read external values safely, even in concurrent mode.

What Does useSyncExternalStore Do?

#

What Does useSyncExternalStore that takes two arguments. subscribe(callback) allows us to listen for external changes and getSnapshot() allows us to read the current value.


_10
const state = useSyncExternalStore(subscribe, getSnapshot);

React uses useSyncExternalStore to read external data in a way that plays nice with concurrent features like startTransition, Suspense, and selective hydration. It makes sure

  • The value you read during render is consistent
  • The component re-renders correctly when external data changes

Building A Custom Store with useSyncExternalStore

#

Let’s walk through creating a tiny, reactive store from scratch, like a stripped-down version of Zustand.

Firstly let’s create the store:


_26
type Listener = () => void;
_26
_26
function createDogStore() {
_26
let dogName = "Rex";
_26
const listeners = new Set<Listener>();
_26
_26
const getSnapshot = () => dogName;
_26
_26
const subscribe = (listener: Listener) => {
_26
listeners.add(listener);
_26
return () => listeners.delete(listener);
_26
};
_26
_26
const setDogName = (name: string) => {
_26
dogName = name;
_26
listeners.forEach((listener) => listener());
_26
};
_26
_26
return {
_26
getSnapshot,
_26
subscribe,
_26
setDogName,
_26
};
_26
}
_26
_26
export const dogStore = createDogStore();

Simple right? Now we can hook it up in a component.


_12
_12
import { useSyncExternalStore } from 'react';
_12
import { dogStore } from './dogStore';
_12
_12
function DogNameDisplay() {
_12
const dogName = useSyncExternalStore(
_12
dogStore.subscribe,
_12
dogStore.getSnapshot
_12
);
_12
_12
return <h1>Good boy: {dogName}</h1>;
_12
}

Now anytime dogStore.setDogName() is called, the component re-renders safely, even in concurrent React.

Syncing with localStorage

#

Now let’s take a common use case- syncing a piece of UI with a value in localStorage.


_32
function createLocalStorageStore(key: string, defaultValue: string) {
_32
let value = localStorage.getItem(key) ?? defaultValue;
_32
const listeners = new Set<() => void>();
_32
_32
const getSnapshot = () => value;
_32
_32
const subscribe = (listener: () => void) => {
_32
listeners.add(listener);
_32
_32
const storageListener = (e: StorageEvent) => {
_32
if (e.key === key) {
_32
value = e.newValue || '';
_32
listeners.forEach((fn) => fn());
_32
}
_32
};
_32
_32
window.addEventListener('storage', storageListener);
_32
_32
return () => {
_32
listeners.delete(listener);
_32
window.removeEventListener('storage', storageListener);
_32
};
_32
};
_32
_32
const setValue = (newVal: string) => {
_32
value = newVal;
_32
localStorage.setItem(key, newVal);
_32
listeners.forEach((fn) => fn());
_32
};
_32
_32
return { getSnapshot, subscribe, setValue };
_32
}

Now using it in the component is as simple as:


_16
const dogNameStore = createLocalStorageStore("dog-name", "Luna");
_16
_16
function DogNameComponent() {
_16
const dogName = useSyncExternalStore(
_16
dogNameStore.subscribe,
_16
dogNameStore.getSnapshot
_16
);
_16
_16
return (
_16
<><inputvalue={dogName}
_16
onChange={(e) => dogNameStore.setValue(e.target.value)}
_16
/>
_16
<p>Your dog's name is {dogName}</p>
_16
</>
_16
);
_16
}

As always there’s a few gotchas relating to how we construct our hook.

Firstly, always make sure getSnapshot returns the latest value and make sure subscribe should immediately connect the listener (don’t debounce or delay).

Avoid storing complex mutable values like objects unless you know what you’re doing—prefer primitives or stable references.

Finally you don’t need to manually trigger re-renders with forceUpdate anymore- React will handle that!

Conclusion

#

If you’ve ever struggled with syncing React state with some “outside” world such as localStorage, WebSocket feeds, or a global store then useSyncExternalStore is your new best friend.

It’s React’s answer to the question:

How do we render consistent, safe UIs when the world is changing out from under us?

Halpy coding,

DANNY

MDX Quiz

#

Reinforce Your Learning

One of the best ways to reinforce what your learning is to test yourself to solidify the knowlege in your memory.
Complete this 3 question quiz to see how much you remember.

1) What problem does useSyncExternalStore solve in React 18?
file under:
React

Thanks alot for your feedback!

The insights you share really help me with improving the quality of the content here.

If there's anything you would like to add, please send a message to:

[email protected]

Was this article this helpful?

D is for danny

About the author

Danny Engineering

A software engineer with a strong belief in human-centric design and driven by a deep empathy for users. Combining the latest technology with human values to build a better, more connected world.

Gobacktothetop

Made with 🥰 in 🏴󠁧󠁢󠁥󠁮󠁧󠁿

©2025 All rights reserved.