ReactJS - useSyncExternalStore Hook



The new hook introduced by React version 18 is the ‘useSyncExternalStore’ hook. This hook is useful to let us subscribe to an external store. So in this tutorial we will discuss this hook and see examples to understand it better.

The useSyncExternalStore is a React feature that allows our component to subscribe to other data sources, such as third-party libraries or browser APIs, and immediately update as the data changes.

Syntax

const ss= useSyncExternalStore(subscribe, getSnapshot)

Parameter

  • subscribe − The "subscribe" function lets our component know when things change in the store and triggers re-rendering.

  • getSnapshot − The "getSnapshot" function provides a way to see what is in the store. And helping React know when to update our component. These two functions work together to keep our component in sync with external data.

Return Value

The useSyncExternalStore returns the current store snapshot, which we can use in our rendering logic.

How to use it?

To read a value from an external data store, call useSyncExternalStore at the top level of our component. The following is a basic example of how we can use the useSyncExternalStore hook −

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

function NotesApp() {
   const notes = useSyncExternalStore(notesStore.subscribe, notesStore.getSnapshot);
   // ...
}

We can use this hook in four different ways. First is by Subscribing to a third-party shop, second by a browser API subscription, third by transferring the logic to a custom Hook and last by adding server rendering support. So, using the examples, we will examine these one by one −

Examples

Subscribing to a third-party shop

Most of our React components get their data with the help of properties (props), internal state, or context. But a component can need data from sources outside of React that can change over time. These sources can include −

  • Third-Party State Management Libraries are the libraries created by other developers to assist manage the data that our component requires. They keep this information outside of our component and can modify it as required.

  • Browser APIs are methods supplied by web browsers that enable our component to obtain information. Some of this data can change, and the browser will notify our component if this occurs.

Example

So we will be creating a small application, in which we will be having two files: userStore.js for the user store, UserProfile.js for the React component that uses useSyncExternalStore and App.js to use the UserProfile component.

The UserProfile component uses the useSyncExternalStore hook to display user data from the external userStore. When we click the "Change User" button, the user data in the store is updated, and the component automatically shows those changes.

userStore.js

let user = { id: 1, name: "Alia" };
let userListeners = [];

export const userStore = {
   updateUser(newUser) {
      user = newUser;
      emitUserChange();
   },
   subscribeUser(listener) {
      userListeners.push(listener);
      return () => {
         userListeners = userListeners.filter((l) => l !== listener);
      };
   },
   getUserSnapshot() {
      return user;
   }
};

function emitUserChange() {
   for (let listener of userListeners) {
      listener();
   }
}

UserProfile.js

import React from "react";
import { useSyncExternalStore } from "react";
import { userStore } from "./userStore";

function UserProfile() {
   const user = useSyncExternalStore(
      userStore.subscribeUser,
      userStore.getUserSnapshot
   );
   
   return (
      <div>
         <h2>User Profile</h2>
         <p>Name: {user.name}</p>
         <button onClick={() => userStore.updateUser({ id: 1, name: "Shantanu" })}>
            Change User
         </button>
      </div>
   );
}

export default UserProfile;

App.js

import React from "react";
import UserProfile from "./UserProfile";

function App() {
   return (
      <div>
         <h1>React User Profile Example</h1>
         <UserProfile />
      </div>
   );
}

export default App;

Output

user profile

A browser API subscription

Sometimes we need our React component to display information that the web browser provides and changes over time. For example, we want to display that our device is now connected to the internet. This information is provided by the web browser with the help of a property called navigator.onLine.

Because this value might change without React being aware of it, we need to use useSyncExternalStore to keep our component up to date with this changing data. As a result, our component will always display the accurate status of our internet connection.

Example

In this example, we will manage the temperature data and changes within the component itself in the App.js file. We use useState and useEffect to subscribe to temperature changes and update the component every 5 seconds when the temperature changes.

import React, { useEffect, useState } from 'react';

// Changes in temp
let temp = 20;
let tempListeners = [];

function getTemperatureSnapshot() {
   return temp;
}

function subscribeToTemperature(callback) {
   tempListeners.push(callback);
   
   // Every 5 seconds, try retrieving the temp
   const tempInterval = setInterval(() => {
      temp = getRandomTemperature();
      emitTemperatureChange();
   }, 5000);
   
   return () => {
      tempListeners = tempListeners.filter(l => l !== callback);
      clearInterval(tempInterval);
   };
}

function emitTemperatureChange() {
   for (let listener of tempListeners) {
      listener();
   }
}

function getRandomTemperature() {
   return Math.floor(Math.random() * 30) + 10; // Simulated temp range: 10°C to 40°C
}

export default function App() {
   const [currentTemperature, setCurrentTemperature] = useState(getTemperatureSnapshot);
   
   useEffect(() => {
      const unsubscribe = subscribeToTemperature(() => {
         setCurrentTemperature(getTemperatureSnapshot);
      });
      
      return () => {
         unsubscribe();
      };
   }, []);
   
   return (
   <div>
      <h1>React Temperature Indicator</h1>
      <h2>Current Temperature: {currentTemperature}°C</h2>
   </div>
   );
}

Output

temperature indicator

Transferring the logic to a custom Hook

In most of the cases, we will not write useSyncExternalStore explicitly in our components. We will normally call it from our own custom Hook. This allows us to use the same external store from many components.

Example

In this example, we will create a component called WeatherStatus and in this component the custom hook will simulate checking the status of a weather service. After that useSyncExternalStore hook will be used by the App component to show the service status and weather prediction, which will get updated every 10 seconds. This represents a distinct use case while keeping to a framework.

WeatherStatus.js

import { useSyncExternalStore } from "react";
export function useWeatherStatus() {
   const isServiceOnline = useSyncExternalStore(subscribe, getSnapshot);
   return isServiceOnline;
}
function getSnapshot() {
   // Check the status of a weather service
   return checkWeatherStatus();
}
function subscribe(callback) {
   // Subscribe to updates about the weather service status
   const interval = setInterval(() => {
      callback(checkWeatherStatus());
   }, 10000);
   
   return () => {
      clearInterval(interval);
   };
}
function checkWeatherStatus() {
   // Check the weather status from an external source
   return Math.random() < 0.8;
}

App.js

import React from "react";
import { useWeatherStatus } from "./WeatherStatus";
function ServiceStatus() {
   const isServiceOnline = useWeatherStatus();   
   return (
      <div>
         <h1>Weather Service Status</h1>
         <p>{isServiceOnline ? "Service Online" : "Service Offline"}</p>
      </div>
   );
}
function WeatherForecast() {
   const isServiceOnline = useWeatherStatus();
   
   return (
      <div>
         <h1>Weather Forecast</h1>
         <p>{isServiceOnline ? "Today: Sunny" : "Service Offline"}</p>
      </div>
   );
}
export default function App() {
   return (
      <div>
         <ServiceStatus />
         <WeatherForecast />
      </div>
   );
}

Output

weather status

Adding server rendering support

There are two main things to know while developing a React app that can show pages on the server −

  • Some aspects of our software are only available in web browsers and not on the server. As a result, we can't use them while constructing the first page on the server.

  • The data on the server and the data in the client's browser must be the same. This makes sure that the information displayed on the first server-rendered page matches the information displayed when the page fully loads in the user's browser. Keeping consistency in data is critical for an effective user experience.

Example

In this example, we will create a component called OnlineStatus and call it in the App component. The useOnlineStatus hook will manage the online status by making use of the available getSnapshot and getServerSnapshot methods to maintain integrity between server-rendered HTML and client-rendered data. The App component displays the server's online status, and data integrity is maintained between server and client.

OnlineStatus.js

import { useSyncExternalStore } from 'react';

export function useOnlineStatus() {
   const isOnline = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
   return isOnline;
}
function getSnapshot() {
   return window.navigator.onLine;
}

function getServerSnapshot() {
   return true; // Assume the server is always "Online" when generating HTML
}

function subscribe(callback) {
   window.addEventListener('online', callback);
   window.addEventListener('offline', callback);
   
   return () => {
      window.removeEventListener('online', callback);
      window.removeEventListener('offline', callback);
   };
}

App.js

import React from "react";
import { useOnlineStatus } from "./OnlineStatus";

function OnlineStatusDisplay() {
   const isOnline = useOnlineStatus();
   
   return (
      <div>
         <h1>Online Status</h1>
         <p>{isOnline ? "Online" : "Offline"}</p>
      </div>
   );
}

export default function App() {
   return (
      <div>
         <OnlineStatusDisplay />
      </div>
   );
}

Output

online status

Limitations

  • The "getSnapshot" method should provide us with a snapshot of what is available in the store that does not change. If the store contains changeable data, ensure that whenever it changes, we supply React with a fresh, unchanging snapshot of that data.

  • React believes we are listening to something new if we use a different "subscribe" function during a re-render, so it subscribes again. To avoid this unnecessary subscription, define the "subscribe" method outside of our component.

  • If the store changes during a scheduled update, React can be forced to halt and restart. This is done to ensure that everything on the screen represents the most recent version of the shop, even if there has been a rapid update.

  • It is not advisable to halt a render depending on the value provided by "useSyncExternalStore."

reactjs_reference_api.htm
Advertisements