{"id":17483,"library":"zustand-computed","title":"Zustand Computed State Middleware","description":"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.","status":"active","version":"2.1.2","language":"javascript","source_language":"en","source_url":"https://github.com/chrisvander/zustand-computed","tags":["javascript","zustand","computed","calculated","state","react","plugin","middleware","npm","typescript"],"install":[{"cmd":"npm install zustand-computed","lang":"bash","label":"npm"},{"cmd":"yarn add zustand-computed","lang":"bash","label":"yarn"},{"cmd":"pnpm add zustand-computed","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Peer dependency for Zustand stores typically used in React applications.","package":"react","optional":false},{"reason":"Core state management library that this middleware extends.","package":"zustand","optional":false}],"imports":[{"note":"The `computed` middleware was replaced by `createComputed` function in v2.0.0. Ensure you use named import.","wrong":"import computed from 'zustand-computed' // Breaking change in v2.0.0","symbol":"createComputed","correct":"import { createComputed } from 'zustand-computed'"},{"note":"While not directly from `zustand-computed`, `create` from `zustand` is essential. In newer Zustand versions, `create` is typically a named export.","wrong":"import create from 'zustand'","symbol":"create","correct":"import { create } from 'zustand'"},{"note":"When using TypeScript, it's recommended to define a specific type for your computed state, separate from your base store state, for clear type inference and safety.","symbol":"ComputedStore","correct":"type ComputedStore = { countSq: number }"}],"quickstart":{"code":"import { create } from 'zustand'\nimport { createComputed } from 'zustand-computed'\n\ntype Store = {\n  count: number\n  inc: () => void\n  dec: () => void\n}\n\ntype ComputedStore = {\n  countSq: number\n}\n\nconst computed = createComputed((state: Store): ComputedStore => ({\n  countSq: state.count ** 2,\n}))\n\n// To integrate with other middleware like devtools or immer, wrap them around computed\n// For example: create<Store>()(devtools(computed(...))) or create<Store>()(immer(computed(...)))\n\nconst useStore = create<Store>()(\n  computed(\n    (set, get) => ({\n      count: 1,\n      inc: () => set((state) => ({ count: state.count + 1 })),\n      dec: () => set((state) => ({ count: state.count - 1 })),\n      // get() function inside the store definition has access to both base and computed state\n      square: () => set(() => ({ count: get().countSq })),\n      root: () => set((state) => ({ count: Math.floor(Math.sqrt(state.count)) })),\n    })\n  )\n)\n\n// Example usage (e.g., in a React component)\nfunction Counter() {\n  const { count, countSq, inc, dec } = useStore()\n  return (\n    <div>\n      <span>Count: {count}</span>\n      <br />\n      <span>Count Squared: {countSq}</span>\n      <br />\n      <button onClick={inc}>+1</button>\n      <button onClick={dec}>-1</button>\n    </div>\n  )\n}\n\n// To demonstrate outside React, for example in Node.js:\nconst unsubscribe = useStore.subscribe((state) => {\n  console.log('Current state:', state.count, 'Computed:', state.countSq);\n});\n\nuseStore.getState().inc(); // Increment count, triggering recomputation\nuseStore.getState().inc();\n\nunsubscribe();","lang":"typescript","description":"This quickstart demonstrates how to define a Zustand store with computed state using `createComputed`, including TypeScript types, and basic state manipulation. It shows how the computed property (`countSq`) becomes part of the store's observable state and is accessible like any other state property."},"warnings":[{"fix":"Change `import computed from 'zustand-computed'` to `import { createComputed } from 'zustand-computed'`. Update usage from `create(computed(...))` to `create(createComputed(...)(...))`.","message":"The `computed` middleware was replaced by `createComputed` function. This requires changing your import and how you apply the middleware.","severity":"breaking","affected_versions":">=2.0.0"},{"fix":"No direct fix needed if upgrading to v2.1.0 or later, as the problematic Proxy behavior has been removed. Ensure your logic doesn't rely on Proxy-specific behaviors.","message":"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.","severity":"gotcha","affected_versions":">=2.1.0"},{"fix":"Ensure you are on `zustand-computed` v2.1.2 or newer, which includes specific fixes for Immer compatibility. Review middleware order, typically `immer(computed(...))` or `devtools(immer(computed(...)))`.","message":"When combining `zustand-computed` with other Zustand middleware like `immer`, compatibility issues (e.g., throwing errors) have been reported and fixed in recent versions.","severity":"gotcha","affected_versions":">=2.1.1"},{"fix":"No direct user fix is needed for this internal change. However, if you experience bundling issues with older `zustand-computed` versions, ensure you're on a recent version. This was an internal fix.","message":"The internal import path for `zustand` changed from `zustand` to `zustand/vanilla` in a previous version, which could affect bundling or specific environments.","severity":"gotcha","affected_versions":">=1.4.1"},{"fix":"Pass an options object to `createComputed` with `keys` (an array of state keys to watch) or `shouldRecompute` (a function comparing previous and next state) to control when recomputation occurs.","message":"By default, the compute function runs on every store change. For complex computations, this can be inefficient. Optimization options are available.","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"last_verified":"2026-04-22T00:00:00.000Z","next_check":"2026-07-21T00:00:00.000Z","problems":[{"fix":"Ensure `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'`.","cause":"`computed` was used as a middleware directly, instead of being called as a function, or the wrong import for `create` was used.","error":"TypeError: store.use is not a function"},{"fix":"Ensure 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(...))`.","cause":"TypeScript compiler error indicating that the computed properties are not correctly merged into the store's type definition.","error":"Property 'computedValue' does not exist on type 'StoreType'."},{"fix":"Update 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(...))`.","cause":"Using `zustand-computed` with `immer` middleware where computed state changes are incorrectly handled or trigger unexpected Immer behavior.","error":"Error: An Immer producer returned a new value without a change."}],"ecosystem":"npm","meta_description":null}