The Smart React Hook for External Subscriptions
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:
useSyncExternalStoreis 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 changedlisteners— An array that holds functions (event listeners) to be called whenever there’s a change in the state.increaseValue()— A method that increments thevalueby 1, and then calls theemitChange()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 theincreaseValue()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 usingexternalStore.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
useSyncExternalStoreextends 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 touseEffectin 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.
