What You’ll Learn
This guide will show you how to use the `useSyncExternalStore` hook in React. You’ll learn how to manage state that comes from outside your React application, like browser events or custom stores. We’ll cover how it simplifies code compared to `useEffect` and show you practical examples, including managing modal visibility and building your own global state management system.
Prerequisites
- Basic understanding of React and its core concepts.
- Familiarity with React Hooks like `useState` and `useEffect`.
Understanding the Problem with External State
Sometimes, your React app needs to know about information that lives outside of React itself. This could be something like the browser’s online status, a native HTML element’s state, or data managed by a separate JavaScript library.
Traditionally, you might use the `useEffect` hook to connect this external data to your React component’s state. This involves setting up event listeners to detect changes and then updating your component’s state. However, this approach can become complex, especially when dealing with cleanup logic and preventing unintended state updates from other parts of your code.
For instance, imagine tracking a user’s online status. The browser handles this, and you want your React app to reflect it. Using `useEffect`, you’d add listeners for online/offline events and update a React state variable. The challenge is ensuring this connection is clean, efficient, and doesn’t lead to bugs if state is updated incorrectly elsewhere.
Introducing `useSyncExternalStore`
The `useSyncExternalStore` hook is designed specifically for these situations. It helps you subscribe to external data sources and keep your React components in sync with them. This hook simplifies managing state that originates outside of React, often leading to cleaner and more reliable code than using `useEffect` for the same purpose.
How `useSyncExternalStore` Works
The `useSyncExternalStore` hook takes two main arguments:
- `subscribe` function: This function tells React how to listen for changes in the external data source. When the external data changes, this function should trigger a callback.
- `getSnapshot` function: This function provides the current value of the external data. React calls this function to get the latest state.
There’s an optional third argument for server-side rendering, providing a default state when the code runs on the server.
Example 1: Tracking Online Status
Let’s refactor the online status example using `useSyncExternalStore`.
Step 1: Define the `subscribe` function
This function will take a callback. Inside, we set up event listeners for the browser’s online and offline events. When these events fire, we call the provided callback to notify React that the external state has changed.
We also need to return a cleanup function that removes these event listeners when the component unmounts, preventing memory leaks.
Step 2: Define the `getSnapshot` function
This function simply returns the current online status by checking `navigator.onLine`. This gives React the most up-to-date value.
Step 3: Use `useSyncExternalStore` in your component
Replace your `useEffect` logic with `useSyncExternalStore`, passing your `subscribe` and `getSnapshot` functions. React will now automatically handle subscribing to changes and updating your component’s state whenever the browser’s online status changes.
Expert Note: This approach eliminates the need for manual cleanup within `useEffect`, making your code more concise and less prone to errors.
Example 2: Managing Modal Visibility
Consider a modal component using the HTML `
Step 1: Set up the Modal and Refs
Create your modal using the `
Step 2: Define `getSnapshot` for Modal State
The `getSnapshot` function will check if the modal is currently open. You can access this information through the modal’s properties, often available via `modalRef.current?.open` or similar.
If the modal reference isn’t available yet, provide a default value like `false`.
Step 3: Define `subscribe` for Modal Events
The `
Ensure you also return a cleanup function to remove the event listener when the component unmounts.
Step 4: Integrate `useSyncExternalStore`
Use `useSyncExternalStore` with your `subscribe` and `getSnapshot` functions. Now, whenever the modal’s visibility changes (whether through your buttons or browser events like Escape), your React component’s state will automatically stay in sync.
Tip: This pattern is incredibly useful for synchronizing React state with any native browser API or DOM element that manages its own state independently.
Example 3: Creating a Custom Global Store
`useSyncExternalStore` is also powerful for building your own state management solutions, similar to libraries like Zustand or Redux.
Step 1: Define Your Store Logic
Create a separate file (e.g., `todoStore.js`) for your store. Inside, define your state (e.g., an array of to-dos) and functions to modify that state (e.g., `addTodo`, `removeTodo`).
Crucially, manage a list of listeners (subscribers) who want to be notified of state changes. A `Set` is a good data structure for this.
Step 2: Implement `subscribe` and `getSnapshot`
In your store file, export a `subscribe` function that accepts a callback. Add this callback to your listeners set. Return a function that removes the callback from the set (for cleanup).
Export a `getSnapshot` function that returns the current state of your store (e.g., the array of to-dos).
Step 3: Notify Listeners on State Change
Whenever you modify the state within your store (e.g., in `addTodo` or `removeTodo`), iterate through your listeners set and call each listener’s callback. This pushes the new state to any components subscribed to the store.
Step 4: Use Your Custom Store in React Components
In your React components, import your `subscribe` and `getSnapshot` functions from the store file. Use `useSyncExternalStore` with these functions to get the current state and subscribe to updates.
You can then use the store’s action functions (like `addTodo`, `removeTodo`) to update the global state. Changes will automatically reflect in all components using the store.
Warning: When updating state in your custom store, ensure you create new references for arrays or objects if you want React to detect the change correctly. For example, instead of directly pushing to an array, create a new array with the added item.
Benefits of `useSyncExternalStore`
- Simplified State Management: Reduces the complexity of synchronizing external data with React state.
- Cleaner Code: Often replaces verbose `useEffect` setups with a more declarative approach.
- Improved Performance: React can optimize re-renders more effectively by understanding the external data flow.
- Server-Side Rendering Support: Handles server rendering gracefully with its optional third argument.
By using `useSyncExternalStore`, you can build more robust and maintainable React applications, especially when dealing with data sources outside of React’s direct control.
Source: You Need To Start Using This Underrated React Hook (YouTube)