The useSyncExternalStore hook in React is designed to handle subscriptions to external data stores within React components. useSyncExternalStore is especially useful when integrating third-party libraries or APIs that maintain their own state outside of React.
React has continually evolved, introducing powerful hooks and features to enhance state management and component behavior. One of the more recent additions is the useSyncExternalStore
hook, designed to handle subscriptions to external data stores seamlessly. In this article, we dive into the details of this hook and explain its signature, parameters and usage with a helpful example.
useSyncExternalStore
is a hook introduced to manage subscriptions to external data stores in a way that allows consistent updates and snapshots of data. This hook is especially useful when integrating third-party libraries or APIs that maintain their own state outside of React. By leveraging useSyncExternalStore
, we can keep components in sync with external state changes efficiently and reliably.
The useSyncExternalStore
hook has the following signature:
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
Let’s break down each parameter the hook accepts:
subscribe
: Function that sets up a subscription to the external store. It takes a callback as an argument, which should be called whenever the store updates. Ideally, this function should return a cleanup function to remove the subscription.getSnapshot
: Function that returns the current state of the store. It should be consistent between calls if the store hasn’t changed.getServerSnapshot
(optional): Function that returns the initial state of the store for server-side rendering. This is important for ensuring hydration consistency between server and client.The hook returns a snapshot
which is the current state of the external store that can be used in a component’s render logic.
To illustrate the usage of useSyncExternalStore
, let’s consider a simple todos store. This example is adapted from the React documentation.
let nextId = 0;
let todos = [{ id: nextId++, text: "Todo #1" }];
let listeners = [];
export const todosStore = {
addTodo() {
todos = [...todos, { id: nextId++, text: "Todo #" + nextId }];
emitChange();
},
subscribe(listener) {
listeners = [...listeners, listener];
return () => {
listeners = listeners.filter((l) => l !== listener);
};
},
getSnapshot() {
return todos;
},
};
function emitChange() {
for (let listener of listeners) {
listener();
}
}
In the above code example, todosStore
is a simple store that maintains a list of todos. It provides methods to add a todo, subscribe to changes and get the current snapshot of todos. Keep in mind that the above is an example of a third-party store that we might need to integrate with React. When working within React itself, it’s recommended to use React’s built-in state management solutions like useState or useReducer for simpler state management needs.
Let’s now see how a React Todos
component can use this store with the useSyncExternalStore
hook. First, we subscribe to the todosStore
and get the snapshot of todos:
import { useSyncExternalStore } from "react";
import { todosStore } from "./todoStore.js";
export default function Todos() {
const todos = useSyncExternalStore(
todosStore.subscribe,
todosStore.getSnapshot
);
// ...
}
Above, the useSyncExternalStore
hook subscribes to the todosStore
and retrieves the current snapshot of todos. The todos
variable will be updated whenever the store changes.
Next, we can build on this by rendering the list of todos and providing a way to add new todos:
import { useSyncExternalStore } from "react";
import { todosStore } from "./todoStore.js";
export default function Todos() {
const todos = useSyncExternalStore(
todosStore.subscribe,
todosStore.getSnapshot
);
return (
<>
<button onClick={() => todosStore.addTodo()}>Add todo</button>
<hr />
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</>
);
}
In this Todos
component, useSyncExternalStore
is used to subscribe to the todosStore
. The hook re-renders the component whenever the store updates, providing a consistent and up-to-date view of the todos. The Add todo
button allows users to add a new todo, which updates the store and triggers a re-render to display the new todo.
When using the useSyncExternalStore
hook, be sure to consider the following points:
getSnapshot
are immutable. This means returning a new snapshot if the store data changes.subscribe
function changes between re-renders, React will re-subscribe to the store. To prevent this, declare the subscribe
function outside the component.getServerSnapshot
parameter to provide an initial snapshot for server-side rendering. This is so that the server-rendered content is sure to match the client-rendered content during hydration.The useSyncExternalStore
hook provides a powerful and standardized way to integrate external stores with React components. It simplifies the process of subscribing to external data sources and ensures that components stay in sync with external state. Whether you’re working with third-party libraries or building complex state management solutions, useSyncExternalStore
ensures your components stay in sync with external data changes.
For more details on the useSyncExternalStore hook, be sure to check out the official React documentation.
Hassan is a senior frontend engineer and has helped build large production applications at-scale at organizations like Doordash, Instacart and Shopify. Hassan is also a published author and course instructor where he’s helped thousands of students learn in-depth frontend engineering skills like React, Vue, TypeScript, and GraphQL.