Go Back

Synchronize your React component with external store using useSyncExternalStore hook

Posted on August 15, 2023|4 min read

Sometimes we need to sync our react components with external data or external state change. This is often the case when dealing with data that comes from source outside of React such as:

  • subscribing to some value exposed by the browser which can be changed over time and provides events to subscribe to its changes or,
  • third-party state management libraries that hold state outside of React.

Suppose you want to notify user whenever their network status changes. The browser exposes this information via navigator.onLine property. Also, browser emits online and offline events whenever network connectivity status changes.

We can create a custom hook which can hold the network status as state and updates whenever status changes. Here is the simple implementation using useState and useEffect hook:

export const useIsOnline = () => { const [isOnline, setIsOnline] = useState(navigator.onLine); useEffect(() => { const onOnline = () => setIsOnline(true); const onOffline = () => setIsOnline(false); window.addEventListener('online', onOnline); window.addEventListener('offline', onOffline); // remove listener on unmount return () => { window.removeEventListener('online', onOnline); window.removeEventListener('offline', onOffline); } }, []); return isOnline; };

Then in our component/hook, we can use this custom hook and dispatch action to show notification to the user:

const isOnline = useIsOnline(); useEffect(() => { dispatch({ type: 'NETWORK_STATUS_CHANGE', payload: isOnline ? 'ONLINE' : 'OFFLINE', }); }, [isOnline]);

This solution will work fine. Here we are manually subscribing to these events and updating the state. But what if I tell you there is an alternate and much simpler way to synchronise network change state with our component.

Introducing useSyncExternalStore hook

useSyncExternalStore is a new React hook introduced in React 18. It lets you to subscribe to an external store and updates your React component whenever there is any change in external store which it is subscribed to.

Whenever any value can change without React's knowledge, you can read it with useSyncExternalStore hook. It is recommended to use build-in React state with useState and useEffect whenever possible. useSyncExternalStore can be used if you need to integrate with existing non-React code.

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

It accepts two required parameters and one optional parameter:

  • subscribe: This function should subscribe to the store updates, and should return a function that cleans up the subscription. It takes a single callback argument and subscribes it to the store. When the store changes, it should invoke the provided callback which will cause the component to re-render. Note that, if a different subscribe function is passed during a re-render, React will re-subscribe to the store using the newly passed subscribe function. You can prevent this by declaring subscribe outside the component or wrapping your subscribe function with useCallback hook.

  • getSnapshot: This function returns a snapshot of the data in the store. Value returned by this function will be provided to the component. In case the store hasn't changed, you must cache the return value so that repeated calls to it must return the same value. React will re-render the component if its return value is different from the last time. If you always return a different value, you will enter an infinite loop and will trigger re-rendering indefinitely. React will throw an error in this scenario letting you know to cache the function result.

  • optional getServerSnapshot: This function will be used during server rendering and during hydration of server-rendered content on the client.

It returns the current snapshot of the store data which you can use in your rendering logic.

Let's implement useIsOnline custom hook with useSyncExternalStore.

When navigator.onLine changes, the browser fires the online and offline events on the window object. You need to subscribe the callback argument to the corresponding events, and then return a function that cleans up the subscriptions.

// subscribe function which subscribes the browser network events and returns a function to unsubscribe them. function subscribe(callback) { window.addEventListener('online', callback); window.addEventListener('offline', callback); return () => { window.removeEventListener('online', callback); window.removeEventListener('offline', callback); }; } // current snapshot of the store i.e. current network status exposed by browser function getSnapshot() { return navigator.onLine; } // extract the logic to useIsOnline custom hook export const useIsOnline = () => useSyncExternalStore( subscribe, getSnapshot );

Now, you can use useIsOnline the same way it was used before.

Note: In case you want to use useSyncExternalStore hook in older version of React like React 17 or 16, you can use npm package use-sync-external-store which is backward-compatible shim for existing React 18 useSyncExternalStore API.

There is another usecase of useSyncExternalStore which I haven't mentioned which is to fix the issue of over-returning React hooks that leads to unneccessary re-renders. If you are interested, you can go through this awesome article.

Cheers ✌️ !

Happy Independence day 🇮🇳🎈