# Middleware and composition

Middleware wraps a state creator to extend how one Zustand store behaves, while slices let you build that store from smaller state creators.

## How the pieces fit

[`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#create) receives the composed state creator and returns one hook-backed store. A middleware such as [`devtools`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#devtools) wraps that creator; slice creators contribute their fields and actions to the same store.

```mermaid
flowchart TD
  A["create()"] --> B["devtools()"]
  B --> C["Composed state creator"]
  C --> D["Bear slice"]
  C --> E["Fish slice"]
  C --> F["One store and hook"]
```

The state creator is the function that receives `set`, `get`, and the store API and returns initial state. The [`StateCreator`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#statecreator) type lets each slice return only its own fields while typing its arguments against the full store state. Compose the slices into one state creator, then apply middleware to that combined creator—not separately inside each slice.

## Compose slices and wrap the store

Use slices when a growing store benefits from splitting its state and actions by feature. This browser example puts bear and fish state in separate creators, wraps the combined creator with `devtools`, and mounts a React component that displays both counts. Clicking either button updates the matching count in the same store.

```tsx
import { create, type StateCreator } from 'zustand'
import { devtools } from 'zustand/middleware'
import { createRoot } from 'react-dom/client'

interface BearSlice {
  bears: number
  addBear: () => void
  eatFish: () => void
}

interface FishSlice {
  fishes: number
  addFish: () => void
}

type StoreState = BearSlice & FishSlice

const createBearSlice: StateCreator<
  StoreState,
  [['zustand/devtools', never]],
  [],
  BearSlice
> = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
  eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})

const createFishSlice: StateCreator<
  StoreState,
  [['zustand/devtools', never]],
  [],
  FishSlice
> = (set) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})

export const useBoundStore = create<StoreState>()(
  devtools((...args) => ({
    ...createBearSlice(...args),
    ...createFishSlice(...args),
  })),
)

export function App() {
  const bears = useBoundStore((state) => state.bears)
  const fishes = useBoundStore((state) => state.fishes)
  const addBear = useBoundStore((state) => state.addBear)
  const addFish = useBoundStore((state) => state.addFish)

  return (
    <main>
      <p>Bears: {bears}</p>
      <p>Fish: {fishes}</p>
      <button onClick={addBear}>Add bear</button>
      <button onClick={addFish}>Add fish</button>
    </main>
  )
}

const container = document.createElement('div')
document.body.append(container)
createRoot(container).render(<App />)
```

The `eatFish` action demonstrates that a slice can update a field owned by another slice: each creator receives the full store state through its typed `set` argument. The component selects individual values and actions from the combined hook, so the rendered counts reflect updates to the shared store.

## Middleware composition and typing

Apply middleware at the combined-store boundary. Putting middleware in individual slices can cause unexpected issues. With multiple middleware, nest the wrappers directly inside `create` so TypeScript can infer their mutator types. For example, `devtools(`[`persist`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#persist)`(stateCreator, options))` composes persistence inside DevTools. Put `devtools` outermost in the chain because other middleware can modify `setState`; placing `devtools` last ensures its `setState` changes and added type parameter remain in place.

When a slice uses middleware, include the middleware mutator tuple in its `StateCreator` type, as the example does for `devtools`. Keep middleware nesting directly inside `create`; a reusable function that bundles middleware requires more advanced types to preserve contextual inference.

## When to use `combine`

[`combine`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware#combine) is an alternative when you want the initial-state object and the action creator in one place and want TypeScript to infer the state. It merges the initial object and the creator's returned object shallowly. The authors caution that the creator's `get` and `set` types reflect the initial-state parameter rather than the merged result, so replacing state or relying on `Object.keys(get())` can be unsafe. Prefer typed slices when the store is organized into separate feature creators or when you want the full state type available to each slice.

## Related

- [Compose a store from slices](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/compose-a-store-from-slices) for a focused slice-pattern walkthrough.
- [Debug with Redux DevTools](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/debug-with-redux-devtools) for using DevTools with a store.
- [Type a store with TypeScript](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/type-a-store-with-typescript) for the broader Zustand typing patterns.
- Explore the [Zustand live demo](https://zustand-demo.pmnd.rs/).
