avatarJoanna Marie

Summary

The article discusses the use of the useSyncExternalStore React hook to synchronize external state with React components efficiently.

Abstract

The useSyncExternalStore hook in React is designed to facilitate the subscription and synchronization of external state sources, such as third-party state management libraries, global variables, or browser APIs, with React components. This hook takes three functions as arguments: subscribe, which registers a callback to be notified when the external state changes; getSnapshot, which retrieves the current state; and an optional getServerSnapshot, used for server-side rendering. The article provides an example of an external store with methods to manipulate and subscribe to its state, and demonstrates how to create a custom hook using useSyncExternalStore to encapsulate the logic for interacting with the external store. The benefits of using useSyncExternalStore over useEffect for handling external state changes include reduced boilerplate, improved maintainability, and real-time updates, offering a more elegant and consistent approach for integrating external state into React applications.

Opinions

  • The author suggests that useSyncExternalStore can be a more efficient alternative to useEffect for subscribing to external stores, as it can reduce boilerplate and improve code maintainability.
  • The article implies that useSyncExternalStore is particularly useful when integrating with non-React code or external state that changes over time.
  • The author emphasizes the importance of unsubscribing from the external store to prevent memory leaks, which is handled neatly by the cleanup function returned by the subscribe function.
  • By providing a real-world example, the author conveys that useSyncExternalStore can be used in various scenarios, including integration with browser APIs and global state management.
  • The article concludes by highlighting the advantages of useSyncExternalStore, such as real-time updates and a consistent approach across components, which can lead to better performance and developer experience.
  • The author encourages readers to engage with the content by following on social media and considering a cost-effective AI service recommendation.

The Smart React Hook for External Subscriptions

Photo by Clément Falize on Unsplash

Sometimes, you may deal with integrating with existing non-React code and you want to subscribe some external state. This is where a hook called “useSyncExternalStore” can come into play.

Definition of this hook taken from the react documentation:

useSyncExternalStore is a React Hook that lets you subscribe to an external store.

Basically, external store is a store that holds the state outside of the React. State in external store can change over time and some components may need to be notified about those changes. Examples of external store are:

  • third-party state management library
  • global variables
  • your local storage
  • browser api that exposes mutable events and methods to subscribe to its changes.

Subscribe means process of connecting to external store and becoming aware of its changes. It is making external store able to notify us whenever state changes.

How to use this hook?

Following example shows basic usage of useSyncExternalStore hook:

const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);

It returns the snapshot of the data in three store and tooks to paramters:

subscribe

  • Function that subscribe to external store.
  • It tooks callback as a parameter and register this callback to the external store, to make store able to invoke it.
  • Invoking callback will cause component to re-render.
  • It should return cleanup function — which will let to unsubscribe from the external store.

getSnaphot

  • Function that is used to check if the subscribed value has changed since the last time it was rendered.
  • It takes state as the argument and returns snaphot of the data from the store that we subscribe.
  • When this value will be different our component will be re-rendered.

getServerSnaphot (optional)

  • Function that returns the initial snaphot of the data from the store that we subscribe and is used during server-side rendering.

Example

Let’s take a closer look at the following code. First, I’ll create a simulation of the external store:

//externalStore.js

let value = 0;
let listeners = [];

export const externalStore = {
  increaseValue() {
    value += 1;
    emitChange();
  },
  getValue() {
    return value;
  },
  registerEventListener(listener) {
    listeners = [...listeners, listener];
  },
  unregisterEventListener(listener) {
    listeners = listeners.filter(l => l !== listener);
  }
}

function emitChange() {
  for (let listener of listeners) {
    listener();
  }
}

Let’s break down the key components of this code:

  • value — It represents a numerical value that can be changed
  • listeners — An array that holds functions (event listeners) to be called whenever there’s a change in the state.
  • increaseValue() — A method that increments the value by 1, and then calls the emitChange() function, notifying all registered event listeners about the change.
  • getValue()- A method that returns the current value of the store.
  • registerEventListener(listener) — A method for external components to register event listeners. These listeners will be notified whenever there is a change in the state.
  • unregisterEventListener(listener) — A method to remove/unsubscribe a specific event listener.
  • emitChange() — It iterates through all registered listeners and calls each one. This function is called whenever there is a change in the state (specifically in the increaseValue() method).

I also created a custom hook that uses useSyncExternalStore. I designed it this way to avoid duplicating the getSnapshot() and subscribe() methods in multiple components:

// useExternalStore.js

import { useSyncExternalStore } from 'react';
import { externalStore } from './externalStore.js';

export function useExternalStore() {
  const value = useSyncExternalStore(subscribe, getSnapshot);
  return value;
}

function getSnapshot() {
  return externalStore.getValue();
}

function subscribe(callback) {
  externalStore.registerEventListener(callback);
  return () => {
    externalStore.unregisterEventListener(callback);
  }
}
  • subscribe(callback) — It is a function that registers a callback with the external store's event system and it returns a cleanup function that unregisters the callback when the component is unmounted.
  • getSnapshot() — Is a function that returns the current value using externalStore.getValue().

Finally, I can now use my custom hook in a component and receive notifications whenever the state changes:

// App.js

import { externalStore } from './externalStore.js';
import { useExternalStore } from './useExternalStore.js';

function ButtonComponent () {
  return (
    <button onClick={() => externalStore.increaseValue()}>+ 1</button>
  );
}

function TextComponent () {
  const value = useExternalStore();

  return (
    <p>Value: {value}</p>
  );
}

export default function App() {
  return (
    <>
      <ButtonComponent />
      <TextComponent />
    </>
  );
}

ButtonComponent, representing a button that, when clicked, calls the increaseValue method on the externalStore. This method increments the value in the external store by 1 and calls emitChange() method, and then all callbacks.

TextComponentsubscribes to changes in the external store and automatically updates its content when the value changes, because it has registered callback in the external store.

Summary

  • The use of useSyncExternalStore extends beyond just libraries and can include various scenarios like subscribing to browser APIs.
  • it can be a preferred alternative to using useEffect, as it can be more elegant solution compared to useEffect in certain situations, reducing boilerplate and improving code maintainability.
  • It simplifies the process of handling external state changes.
  • It offers real-time updates, and provides a consistent and reusable approach across different components.

Stackademic

Thank you for reading until the end. Before you go:

  • Please consider clapping and following the writer! 👏
  • Follow us on Twitter(X), LinkedIn, and YouTube.
  • Visit Stackademic.com to find out more about how we are democratizing free programming education around the world.
React
JavaScript
Reactjs
React Hook
React State
Recommended from ReadMedium