Zustand Computed State Middleware
Zustand-computed is a lightweight, TypeScript-friendly middleware designed for the Zustand state management library, enabling the creation of derived or 'computed' state based on the existing store state. The current stable version is 2.1.2, released in April 2026. The package maintains an active release cadence, with multiple bug fixes and minor features released within the last year, demonstrating ongoing development and support. Its primary differentiator is its simplicity and direct integration with Zustand's middleware pattern, providing a straightforward way to add calculated properties to a store without manual memoization or complex selectors. It focuses on functional transformations and offers options to optimize recomputation with `keys` or a `shouldRecompute` function, differentiating it from purely selector-based approaches by integrating computed values directly into the store's state tree.
Common errors
-
TypeError: store.use is not a function
cause `computed` was used as a middleware directly, instead of being called as a function, or the wrong import for `create` was used.fixEnsure `createComputed` is imported correctly and called as a function (e.g., `create(createComputed(...)(...))`). Also, confirm `create` is imported from 'zustand' as `import { create } from 'zustand'`. -
Property 'computedValue' does not exist on type 'StoreType'.
cause TypeScript compiler error indicating that the computed properties are not correctly merged into the store's type definition.fixEnsure your `create` call is correctly typed to include both your base `Store` type and the `ComputedStore` type. For example: `create<Store, [['chrisvander/zustand-computed', ComputedStore]]>(computed(...))`. -
Error: An Immer producer returned a new value without a change.
cause Using `zustand-computed` with `immer` middleware where computed state changes are incorrectly handled or trigger unexpected Immer behavior.fixUpdate to `zustand-computed` v2.1.2 or newer to leverage bug fixes specifically for Immer compatibility. Ensure the order of middleware is correct, typically `immer(computed(...))`.
Warnings
- breaking The `computed` middleware was replaced by `createComputed` function. This requires changing your import and how you apply the middleware.
- gotcha Earlier versions used a Proxy for computed state, which could lead to unexpected behavior or performance issues in some scenarios. This was removed in v2.1.0.
- gotcha When combining `zustand-computed` with other Zustand middleware like `immer`, compatibility issues (e.g., throwing errors) have been reported and fixed in recent versions.
- gotcha The internal import path for `zustand` changed from `zustand` to `zustand/vanilla` in a previous version, which could affect bundling or specific environments.
- gotcha By default, the compute function runs on every store change. For complex computations, this can be inefficient. Optimization options are available.
Install
-
npm install zustand-computed -
yarn add zustand-computed -
pnpm add zustand-computed
Imports
- createComputed
import computed from 'zustand-computed' // Breaking change in v2.0.0
import { createComputed } from 'zustand-computed' - create
import create from 'zustand'
import { create } from 'zustand' - ComputedStore
type ComputedStore = { countSq: number }
Quickstart
import { create } from 'zustand'
import { createComputed } from 'zustand-computed'
type Store = {
count: number
inc: () => void
dec: () => void
}
type ComputedStore = {
countSq: number
}
const computed = createComputed((state: Store): ComputedStore => ({
countSq: state.count ** 2,
}))
// To integrate with other middleware like devtools or immer, wrap them around computed
// For example: create<Store>()(devtools(computed(...))) or create<Store>()(immer(computed(...)))
const useStore = create<Store>()(
computed(
(set, get) => ({
count: 1,
inc: () => set((state) => ({ count: state.count + 1 })),
dec: () => set((state) => ({ count: state.count - 1 })),
// get() function inside the store definition has access to both base and computed state
square: () => set(() => ({ count: get().countSq })),
root: () => set((state) => ({ count: Math.floor(Math.sqrt(state.count)) })),
})
)
)
// Example usage (e.g., in a React component)
function Counter() {
const { count, countSq, inc, dec } = useStore()
return (
<div>
<span>Count: {count}</span>
<br />
<span>Count Squared: {countSq}</span>
<br />
<button onClick={inc}>+1</button>
<button onClick={dec}>-1</button>
</div>
)
}
// To demonstrate outside React, for example in Node.js:
const unsubscribe = useStore.subscribe((state) => {
console.log('Current state:', state.count, 'Computed:', state.countSq);
});
useStore.getState().inc(); // Increment count, triggering recomputation
useStore.getState().inc();
unsubscribe();