Aaron Presley


Understanding Zustand Middleware

August 28, 2020

I’ve been diving head-first into the zustand, which is a great library for managing global state in a React application.

Their documentation have examples of middleware, but I was having a really hard time wrapping my head around what it was actually doing.

I wanted two things out of my setup. I want:

  1. To have multiple stores in my app, not one huge one
  2. Each store to have the same middleware applied

I decided how I wanted to declare each store, then worked backward. Here’s how I want to declare a store:

// some-store-1.ts
import { createStore } from './store-helpers'

export interface SomeState1 {
  someVal: 'hello',
  someAction: () => 'world',
}

export const someStore1 = createStore<SomeState1>(`someStore1`, (set, get) => ({
	// state stuff here
}));

The first argument of my function is strictly for making logging more clear when I have multiple stores logging at once.

Now I just need to make my createStore function:

// store-helpers.ts

// Credit to @coffee-cup here:
// https://github.com/react-spring/zustand/issues/75#issuecomment-563017267
export type Middleware<T> = (
  config: StateCreator<T>,
) => (
  set: SetState<T>,
  get: GetState<T>,
  api: StoreApi<T>
) => T;

export const createStore = <T> (storeName: string, fn: StateCreator<T>) => {
  const customMiddleware: Middleware<T> = config => (set, get, api) =>
      config(
        setArgs => {
          // Do stuff here when setting
          set(setArgs);
        },
        () => {
          // Do stuff here when getting
          return get()
        },
        ({
          ...api,
          // Add custom api functions here, though I had
          // trouble getting passing a custom StateApi type here
        }),
      );

  return create(customMiddleware(fn));
}

The above isn’t syntactically pretty, but expanding it this way helped wrap my head around how middleware works. You could have as many of these middleware functions as you like. Like so:

return create(customMiddleware1(customMiddleware2(fn)))

Hope this helps.