Aaron Presley

Back to Writing

Understanding Zustand Middleware

August 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) =>
      setArgs => {
        // Do stuff here when setting
      () => {
        // Do stuff here when getting
        return get()
        // 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.