# A Look At React Hooks: useSyncExternalStore

Welcome to another article of [**A Look at React Hooks**](https://lo-victoria.com/series/a-look-at-react-hooks), a beginner-friendly series on React Hooks. In this article, let's learn about the `useSyncExternalStore` Hook. It is a useful Hook for when you need to integrate non-React state management in your apps.

Before we begin, you should have a basic knowledge of React and how React Hooks work. If not, please read the [React Hook series](https://lo-victoria.com/series/a-look-at-react-hooks) from the beginning.

## What is useSyncExternalStore

`useSyncExternalStore` a custom hook available in React 18, that lets you subscribe to an **external store**, reads updated values from that store and updates components if needed.

By 'external store', it means:

* Third-party state management libraries that hold state outside of React
    
* Browser APIs that expose a mutable value and events to subscribe to its changes
    

Some examples of 'external store' includes:

* Browser history
    
* localStorage
    
* Third-party data sources
    

## Why use useSyncExternalStore

According to the [official docs](https://react.dev/learn/you-might-not-need-an-effect#subscribing-to-an-external-store), this Hook is useful when you want to update React components when some data in external stores changes. Without `useSyncExternalStore` Hook, you would need to manually subscribe to these external stores using `useEffect`.

In some cases, this might cause over-returning Hooks and unwanted re-rendering of components. Hence, it is more optimal to use `useSyncExternalStore`. **Note that the Hook itself doesn't inherently mitigate or address over-re-rendering issues** caused by the use of `useEffect`. Instead, it provides a way to synchronize your component with an external store in a more controlled manner, which may make your code less error-prone.

Here's an example: say you have a chat app like Discord with multiple text channels. You fetch the `messagesData` from an external database like firestore.

You want the app to do 2 things:

1. When a new message is added to `messagesData` by the user, jump to the most recent message to display the newly added message in the window
    
2. When a new message is added to `messagesData` by another user to another text channel, display the newly added message when the user switches text channel
    

So, you might use `useEffect` to achieve these 2 things like this:

```typescript
  const [messagesData, setMessagesData] = useState([]);
  const [currentChannel, setCurrentChannel] = useState('general');
  const messagesEndRef = useRef(null);

  // useEffect to handle jumping to the most recent message when a new message is added
  useEffect(() => {
    // Assuming you have a function to fetch messages from Firestore
    const fetchMessages = async () => {
      const messages = await fetchMessagesFromFirestore(currentChannel);
      setMessagesData(messages);
      scrollToBottom();
    };

    fetchMessages();

    // Subscribe to changes in Firestore for the current channel
    const unsubscribe = subscribeToChannel(currentChannel, (newMessages) => {
      setMessagesData(newMessages);
      scrollToBottom();
    });

    return () => {
      // Clean up the subscription when the component unmounts or the channel changes
      unsubscribe();
    };
  }, [currentChannel]); // Re-run the effect when the current channel changes

  // useEffect to handle displaying a new message when switching channels
  useEffect(() => {
    const unsubscribe = subscribeToChannel(currentChannel, (newMessages) => {
      setMessagesData(newMessages);
      scrollToBottom();
    });

    return () => {
      unsubscribe();
    };
  }, [currentChannel]);

  const scrollToBottom = () => {
    // Scroll to the bottom of the messages container
    if (messagesEndRef.current) {
      messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
    }
  };

  const handleChannelChange = (newChannel) => {
    setCurrentChannel(newChannel);
  };
```

As seen from the example above, the `useEffect` for fetching messages is triggered whenever the `currentChannel` changes. While this is necessary for updating the messages for the new channel, it might lead to unnecessary re-renders if the component is re-rendered for reasons unrelated to the channel change.

## Implementation

Here's a simple example of how to use the Hook:

```javascript
const externalStore = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
```

The Hook returns a snapshot of the external store's data you are subscribed to.

* `subscribe` : Invokes callback function that triggers when component re-renders. It should also handle the cleanup of the subscription.
    
* `getSnapshot` : This fetches and returns a snapshot of the data subscribed. If the returned value is different, then the component re-renders.
    
* `getServerSnapshot` (optional): This function returns the initial snapshot of the data in the store and provides the snapshot during server-side rendering.
    

## Minimalistic Example

Let's see how this Hook works in an example. Let's say you want to listen for changes in `localStorage`.

First, you would import the Hook in your component.

```javascript
import {useSyncExternalStore} from 'react'
```

Then, you would implement the Hook to read data from `localStorage` like this:

```javascript
//this is the subscribe function, listens for changes
const subscribe = (listener) => {
    window.addEventListener("storage", listener);
    return () => {
      window.removeEventListener("storage", listener);
    };
}
//this is the getSnapShot function, returns data subscribed
const getSnapShot = () => {
    return localStorage.getItem("example");
}
//implement the Hook
const exampleValue = useSyncExternalStore(subscribe, getSnapShot);
```

Keep in mind this is a minimalistic example, but it should give you a gist on how it works. So the `subscribe` function will listen for changes in `localStorage` and handle unsubscribing as well. Then `getSnapShot` gets the value of the data.

Finally, we use the `useSyncExternalStore` to ensure the component re-renders whenever a change is detected from `localStorage`, the external data store we are subscribing to.

> For more examples, do visit the [official website](https://react.dev/reference/react/useSyncExternalStore) on useSyncExternalStore.

## Another Example

How about we go back to the chat app example mentioned earlier? How can we use `useSyncExternalStore` to simplify synchronization with our external database?

As shown in the example below, using this Hook allows a centralized approach to handle data from external sources and a more simplified code for real-time data updates.

```typescript
  const [currentChannel, setCurrentChannel] = useState('general');
  const messagesEndRef = useRef(null);

  // useSyncExternalStore for fetching messages
  const messages = useSyncExternalStore(
    () => subscribeToChannel(currentChannel, updateMessages),
    () => fetchMessagesFromFirestore(currentChannel),
    null // No server snapshot in this example, but you can add it if needed
  );

  useEffect(() => {
    scrollToBottom();
  }, [messages]);

  const updateMessages = (newMessages) => {
    // Handle the newly received messages
    // This callback will be invoked whenever there's a change in the external store
  };

  const scrollToBottom = () => {
    if (messagesEndRef.current) {
      messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
    }
  };

  const handleChannelChange = (newChannel) => {
    setCurrentChannel(newChannel);
  };
```

## Conclusion

In conclusion, the `useSyncExternalStore` Hook has been a great and useful addition to React's kit. It would take time to learn some of the caveats of this Hook and fully understand how to implement it, so I hope this article is a good starting point for you.

Thanks for reading! Please share and like the article if it has been a helpful read. Also, do share in the comments your thoughts on this React Hook. Feel free to check out the References section below to read more. Till next time, cheers!

## References

* [https://react.dev/reference/react/useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore)
    
* [https://react.dev/learn/you-might-not-need-an-effect#subscribing-to-an-external-store](https://react.dev/learn/you-might-not-need-an-effect#subscribing-to-an-external-store)
    

### **Let's Connect!**

* [**Twitter**](https://twitter.com/lo_victoria2666)
    
* [**Showwcase**](https://www.showwcase.com/victoria-lo)
    
* [**LinkedIn**](https://www.linkedin.com/in/victoria2666/)
    
* [**GitHub**](https://github.com/victoria-lo)
