4 min read

React State Sync with useSyncExternalStore

React State Sync with useSyncExternalStore cover image

Introduction

React hooks have revolutionized how we manage state and side effects in functional components. With the introduction of hooks like useState, useEffect, and useContext, developers have gained powerful tools to handle component logic in a more readable and maintainable way. However, as applications become complex, there's often a need to synchronize component state with external data sources, such as global stores or browser APIs.

Enter useSyncExternalStore, a hook introduced in React 18. This hook is designed to handle synchronization with external stores, ensuring your component state stays consistent with the source of truth, whether it's a global store, an API, or even browser-specific events like window resize or online status changes.

In this blog post, we'll explore the useSyncExternalStore hook in detail, understand its syntax and usage, and see practical examples of how it can be used to connect with various window APIs. By the end, you'll have a solid understanding of how to leverage this hook to maintain a seamless state synchronization in your React applications.

What is useSyncExternalStore?

The useSyncExternalStore hook is a React hook introduced in React 18 that provides a way to read and subscribe to an external store. This is particularly useful for synchronizing component state with external data sources, such as global stores, APIs, or browser events. Unlike useState or useEffect, which are primarily used for internal state and side effects, useSyncExternalStore ensures that your component stays in sync with an external source of truth.

Why use useSyncExternalStore?

Using useSyncExternalStore is beneficial in scenarios where your application needs to maintain a consistent state with an external data source. Here are some key benefits:

  • Consistent State Management: Ensures that your component state is always in sync with the external store.
  • Performance Optimization: Minimizes unnecessary re-renders by subscribing directly to the store changes.
  • Seamless Integration: Simplifies the process of connecting your components to various external sources like APIs or browser events.

Basic syntax and usage

Here's the basic syntax for the useSyncExternalStore hook:

typescript

const value = useSyncExternalStore( subscribe, getSnapshot, getServerSnapshot )
  • subscribe: A function that registers a callback to be called whenever the external store changes. This function should be cached; if a different subscribe function is passed during a re-render, React will re-subscribe to the store using the newly passed function. You can prevent this by declaring the subscribe function outside the component or wrapping it in useCallback.
  • getSnapshot: A function that returns the current value from the external store. The value returned from this function must be immutable. If the underlying store has mutable data, return a new immutable snapshot if the data has changed. Otherwise, return a cached last snapshot.
  • getServerSnapshot: A function that returns the initial value used during server-side rendering.

Example: Synchronizing with Window Scroll Position

Synchronizing whit the window scroll position can be useful for creating components that react to scrolling.

typescript

import { useSyncExternalStore } from "react"; let cache = { scrollY: 0, scrollX: 0, }; function subscribe(callback: () => void) { window.addEventListener("scroll", callback); return () => { window.removeEventListener("scroll", callback); }; } function getSnapshot() { if (cache.scrollY !== window.scrollY || cache.scrollX !== window.scrollX) { cache = { scrollY: window.scrollY, scrollX: window.scrollX, }; } return cache; } function getServerSnapshot() { // defaults to 0 for SSR return cache; } function ScrollPosition() { const position = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); return ( <div> <p>Scroll position: X {position.scrollX}, Y: {position.scrollY}</p> </div> ) }
  • The subscribe function registers a callback to be called whenever the window's scroll event is triggered. This function adds an event listener for the scroll event and returns a cleanup function to remove the listener.
  • The getSnapshot function returns the current scroll positions. If the scroll position has changed, it updates the cache with the new values and returns the updated cache. This ensures the returned value is always up-to-date and immutable.
  • The getServerSnapshot function returns the initial scroll positions for server-side rendering (SSR). It defaults to the values in the cache, which are initialized to zero.

Conclusion

By integrating useSyncExternalStore into your React projects, you can streamline state synchronization with external sources, making your components more robust and responsive to changes. Whether you're dealing with global stores or browser APIs, this hook offers a straightforward and effective solution for maintaining consistent state across your application.

For more detailed information and further reading check out the official React documentation on useSyncExternalStore. Happy coding!