- Daishi Kato's Read the Code
- Posts
- How Zustand Supports Middleware
How Zustand Supports Middleware
Learning Zustand in JavaScript Without Types Is Easier Than With Types
Hi,
Today, I’d like to talk about how Zustand supports middleware. Zustand is a tiny library with two parts: the vanilla part and the React part. You can check out the source code here:
Together, they contain just 43 lines of code. Zustand’s core in JavaScript is very small. (The TypeScript types are more complex, but that's not today’s focus.)
Understanding Zustand’s createStore
Middleware in Zustand is not a built-in feature—there’s no special logic for it in the library. Instead, it’s a capability that naturally emerges from how createStore
is designed.
Here’s a simplified version of Zustand’s vanilla implementation:
export const createStore = (createState) => {
let state;
const listeners = new Set();
const setState = (nextState) => {
state = Object.assign({}, state, nextState);
listeners.forEach((listener) => listener(state));
};
const getState = () => state;
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const api = { setState, getState, subscribe };
state = createState(setState, getState, api);
return api;
};
Take a moment to look over the whole function. The important line is this one:
state = createState(setState, getState, api);
This line is very Zustand-ish. Technically, api
already includes setState
and getState
, so calling createState(api)
could be enough. However, Zustand’s approach of passing all three arguments allows for a shorthand style like this:
createStore((set) => ({
count: 0,
setOne: () => set(1),
}));
In this case, set
refers to api.setState
. Most of the time, the second (get
) and third (api
) arguments are not needed.
No Middleware-Specific Code, Yet It Works
You may notice that there’s no code specifically handling middleware. That’s the beauty of it—the fact that createState
receives api
enables middleware. You could even say middleware support was a by-product of this design.
Here’s an example of how a logger middleware works:
const logger = (createState) => (set, get, api) => {
const newSet = (nextState) => {
console.log('Setting next state', nextState);
set(nextState);
};
api.setState = newSet;
return createState(newSet, get, api);
};
// Usage
const store = createStore(
logger((set) => ({
count: 0,
setOne: () => set(1),
}))
);
The logger is just a higher-order function wrapping createState
. It intercepts set
calls, adds logging, and passes the modified set
to the rest of the store setup.
Honestly, I sometimes wonder if this should even be called "middleware"—but in Zustand’s world, that’s the term we use.
In Summary
There is no middleware-specific logic in Zustand’s library code. But because createState
receives the api
object (and its internal methods), we can build higher-order wrappers around it. That’s what we call middleware in Zustand.
Happy coding.
Reply