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:
subscribe
: function that is on state changesgetSnapshot
: function to retrieve a state snapshotgetServerSnapshot
: state snapshot function for SSRselector
: selector function for state slicesisEqual
: comparison function
As you can imagine, a lot of work done by this hook. But it’s surprisingly straightforward to use.