The Orcasound Next prototype has three contexts that should be migrated to Zustand stores:

DataContext

  • global filter state set by UI
  • resulting array of filtered data
  • array of new objects derived from filtered data
  • summary metrics about filtered data

LayoutContext

  • UI states that persist across page routes

NowPlayingContext

  • global audio player controls
  • Web Audio API analyser node

Migrating these is actually fairly straightforward, it’s mostly like this:

  • Install – npm install zustand
  • Add stores in /stores/exampleStore.tsx
  • Set up store: ```ts import { create } from “zustand”;`

type ExampleStore = { myState: boolean; setMyState: (value: boolean) => void; };

export const useExampleStore = create((set) => ({ myState: true, setMyState: (value) => set({ myState: value }), }));


But there are a couple of gotchas I am coming across, as below.
___
3. **Selector-based subscriptions** 

Context can trigger unnecessary global re-renders when a local component changes the state. Zustand behaves the same way unless you specifically target one slice of state.

For example if I have a LayoutContext provider, I typically access global state from any nested component within `<LayoutContext>{children}</LayoutContext>` like this:
```ts
const { alertOpen, setAlertOpen } = useLayout()

But every time I use setAlertOpen, I am at risk of triggering re-renders to everything inside the LayoutContext provider.

With Zustand, the default approach does the same thing:

const { alertOpen, setAlertOpen } = useLayoutStore()

This subscribes to the entire store, so the component re-renders if there is a change to any unrelated state in that store (e.g. activeMobileTab, drawerContent).

To avoid this, we need to subscribe only to specific slices from the store like this:

const alertOpen = useLayoutStore((state) => state.alertOpen);
const setAlertOpen = useLayoutStore((state) => state.setAlertOpen);

This is more verbose, but we can also export a custom hook for each slice like this, to make it more streamlined:

export const useAlertOpen = () => useLayoutStore((s) => s.alertOpen);
export const useSetAlertOpen = () => useLayoutStore((s) => s.setAlertOpen);

In components:

const alertOpen = useAlertOpen();
const setAlertOpen = useSetAlertOpen();

  1. Custom state setters

React has a built-in SetStateAction in the useState hook that can take either a value (e.g. ‘true’) or a function (e.g. (prev) => !prev). Zustand does not have this – each state setter needs custom handling for different inputs.

So you can make your Zustand state setter functions look like React ones:

setAlertOpen(true);

But you have to define that behavior explicitly in the Zustand store:

setAlertOpen: (value: boolean) => set({ alertOpen: value })

And where React allows you to pass a function like this:

setAlertOpen(prev => !prev);

In Zustand you might define a specific action for that:

toggleAlertOpen()

In store:

toggleAlertOpen: () => set((state) => ({ alertOpen: !state.alertOpen }))

Or, make the state setter responsive to either a value or function:

setAlertOpen: (input) => 
  set((state) => ({ 
    alertOpen: 
      typeof input === "function" ? input(state.alertOpen) : input,
    }))

Anyway, that’s progress for now – let me know any thoughts!