use-Sync-External-Store-With-Selector - What am I looking at?


TL;DR

useSyncExernalStoreWithSelector is a standard hook available from React 18+. It binds an external state into the React state and rendering system. And it’s quite fun to work with!

Zustand” or “Where’s my state?”

Zustand is a fairly simple but nevertheless strong proposal to a non-trivial problem. How do we manage state in React? And by that I don’t mean the scoped state of a single component but rather complex, shared state. There are reducers and the context API, but then your once decoupled component is bound to a surrounding context provider.

Zustand gives you a create function. You state what state you want to work with and with a few tricks it returns a reusable, state sharing hook. This hook accepts a selector to get what you want from the big, “global” hook-store and a comparison function to test whether the data changed.

import create from "zustand";

type ShopState = {
  productIds: string[];
  cart: string[];
  addProductToCart(id: string): void;
  removeProductFromCart(index: number): void;
  fetchAllProducts(): Promise<void>;
};

export const useShopState = create<ShopState>((set) => ({
  productIds: [],
  cart: [],
  addProductToCart(id) {
    //...
  },
  removeProductFromCart(index) {
    //...
  },
  async fetchAllProducts() {
    //...
  },
}));

//...

const products = useShopState((s) => s.productIds);

This is a surprisingly fresh and slim approach to an otherwise difficult situation. In other libraries, you’d be surrounded by Providers, reducers, dispatches, selectors. In some areas, it’s really difficult to wrap your head around all the state changed triggering a rerender. For your newly-created state accessor, you change your comparison function.

I hand you an example. We create a cart array holding all our products we want to purchase.

If we select it like this

const cart = useShopState((s) => s.cart);

The component will rerender every time, because [] !== []. We’ll need to add the provided shallow comparison, to compare array contents:

import shallow from "zustand/shallow";

const cart = useShopState((s) => s.cart, shallow);

Here is a quick implementation:

Now, with all of this said, where does our state live? In the component? In a global provider? Do we need a HOC? No. It lives in a sync external store. In Zustand, it’s a simple state variable (src) that’s created by calling create.

🪄 Magic hook overview

And it’s bound into the React rendering and state handling via a library-hook.

useSyncExernalStoreWithSelector takes 5 props:

As you can imagine, a lot of work done by this hook. But it’s surprisingly straightforward to use.