# Type stores and middleware

Use this pattern to type state and actions, infer them from initial values, reuse the store's type, and compose middleware without losing inference. The React example renders a bear count; clicking **Increase by 1** updates that count.

## 1. Annotate state and actions with curried create

In your React TypeScript project, pass your state-and-actions type to [`create`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#create) using `create<BearState>()(...)`. The extra `()` lets you annotate the state while TypeScript infers the middleware type parameters. Your store is a hook: select the state and actions each component needs.

```ts title="store.ts"
import { create } from 'zustand'

export type BearState = {
  bears: number
  increase: (by: number) => void
}

export const useBearStore = create<BearState>()((set) => ({
  bears: 0,
  increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
```

TypeScript checks that the creator returns every field in `BearState`. It infers `by` as `number`, so you do not need to annotate the action parameter again.

Render the hook's selected values in a component. `BearSummary` uses the same state type for its props rather than repeating the type of `bears`.

```tsx title="App.tsx"
import { useBearStore, type BearState } from './store'

function BearSummary({ bears }: Pick<BearState, 'bears'>) {
  return <p>{bears} bears around</p>
}

export default function App() {
  const bears = useBearStore((state) => state.bears)
  const increase = useBearStore((state) => state.increase)

  return (
    <main style={{ minHeight: 200 }}>
      <BearSummary bears={bears} />
      <button onClick={() => increase(1)}>Increase by 1</button>
    </main>
  )
}
```

Keep the HTML entry point and `main.tsx` from [React quick start](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/react-quick-start#3-mount-the-counter); replace its `store.ts` and `App.tsx` with the files above to reuse `BearState` for component props in the mounted counter.

Here, `BearState` also supplies the component prop type; see [State and actions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/state-and-actions) for the bound hook and its store methods.

## 2. Infer state with combine and extract its type

Replace `store.ts` with the following file; keep `App.tsx` and `main.tsx` unchanged. [`combine`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand-middleware#combine) merges the initial state with the object returned by its additional state creator. It returns a state creator, not a store, so pass it to `create`. Use [`ExtractState`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#extractstate) to export the inferred state-and-actions type.

```ts title="store.ts"
import { create, type ExtractState } from 'zustand'
import { combine } from 'zustand/middleware'

export const useBearStore = create(
  combine({ bears: 0 }, (set) => ({
    increase: (by: number) => set((state) => ({ bears: state.bears + by })),
  })),
)

export type BearState = ExtractState<typeof useBearStore>
```

Use the non-curried `create(...)` here: `combine` creates the state, so TypeScript can infer its type. Annotate `by` because the initial state does not describe that action's parameter.

`ExtractState` extracts the full state-and-actions type from a store's `getState()` method. Here, `BearState` contains both `bears: number` and `increase: (by: number) => void`. The existing `BearSummary` props still compile, and the mounted counter behaves the same way. You can reuse this extracted type in component props, utilities, and tests.

### Keep merging updates unless you intend to replace everything

Passing `true` as the replacement flag replaces the entire state, including actions. Supply a complete state when replacing, or use the default merging update as the examples do.

Be especially careful inside `combine`: its internal `get` type describes only the initial state, although the runtime state also includes the returned actions. A replacement containing only `bears` can compile there while deleting `increase`. Similarly, `Object.keys(get())` includes action keys despite the narrower type. The resulting hook's `getState()` type includes the full state and actions.

## 3. Compose middleware directly inside create

Replace `store.ts` again to add [`persist`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand-middleware#persist) and [`devtools`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand-middleware#devtools). Keep the middleware nested immediately inside curried `create` so contextual inference works. Do not move the composition into an untyped wrapper function.

```ts title="store.ts"
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

export type BearState = {
  bears: number
  increase: (by: number) => void
}

export const useBearStore = create<BearState>()(
  devtools(
    persist(
      (set) => ({
        bears: 0,
        increase: (by) => set((state) => ({ bears: state.bears + by })),
      }),
      {
        name: 'typed-bear-storage',
        partialize: (state) => ({ bears: state.bears }),
      },
    ),
    { name: 'Typed bears' },
  ),
)
```

The same mounted component now saves the bear count to browser `localStorage`. With storage available, refreshing the page restores the saved count. `partialize` selects only `bears` for storage; TypeScript infers that persisted shape while the hook retains the full `BearState` type. Install the [Redux DevTools extension](https://chromewebstore.google.com/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) to inspect the store's updates.

Keep `devtools` outermost when composing it with middleware such as Immer. It adds a type parameter to `setState`; middleware that also modifies `setState` can otherwise lose that parameter.

If you split the creator into slices, apply middleware to the combined store, not inside individual slices. Middleware inside slices can produce unexpected issues. See [Split a store into slices](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/split-a-store-into-slices) for that pattern.

### Options that matter here

For a different JSON-backed storage, use the shipped [`createJSONStorage`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand-middleware#createjsonstorage) adapter. It returns [`PersistStorage`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand-middleware#persiststorage) or `undefined` if obtaining storage throws. See [Persist store data](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/persist-store-data) for storage configuration and hydration behavior.

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `persist` → `name` | `string` | Required | Sets the storage key; use a unique name. |
| `persist` → `partialize` | `(state: S) => PersistedState` | `(state) => state` | Selects the value to persist; the return value determines the persisted type. |
| `persist` → `storage` | `PersistStorage<PersistedState, PersistReturn> \| undefined` | `createJSONStorage(() => window.localStorage)` | Chooses the storage used for persistence. |
| `devtools` → `name` | `string` | Not specified | Names the DevTools connection. |

If you extract a named creator, annotate it with [`StateCreator`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#statecreator), not a hand-written function type. Its generic parameters describe the full state, incoming middleware mutators, outgoing mutators, and the returned slice. A plain `StateCreator<BearState>` describes a creator without middleware mutators; keep middleware-specific mutator information when annotating creators that depend on it.

## Live demo and related tasks

Try the [live Zustand counter demo](https://zustand-demo.pmnd.rs/) to see the hook-and-action interaction. The examples above follow the React starter's separate count and action selectors, with curried creation and middleware typing added here.

- [React quick start](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/react-quick-start) — set up the React integration.
- [Selectors and subscriptions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/selectors-and-subscriptions) — choose what a component reads.
- [Debug store updates](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/debug-store-updates) — name and inspect actions.
- [Reset store state](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/reset-store-state) — initialize and reset state safely.
