Telerik blogs

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

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.

Things to Keep in Mind

When using the useSyncExternalStore hook, be sure to consider the following points:

  1. Immutable snapshots: Make sure that the snapshots returned by getSnapshot are immutable. This means returning a new snapshot if the store data changes.
  2. Consistent subscription function: If the subscribe function changes between re-renders, React will re-subscribe to the store. To prevent this, declare the subscribe function outside the component.
  3. Transition updates: If the store is mutated during a non-blocking transition update, React will treat it as a blocking update for consistency across components.
  4. Server rendering: Use the optional 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.

Wrap-up

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.


About the Author

Hassan Djirdeh

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.

Related Posts

Comments

Comments are disabled in preview mode.