# Use Redux-style reducers

Use [`redux`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand-middleware#redux) when your state changes already fit an action-type and reducer model; it adds a typed `dispatch` function to a Zustand store.

## When to use it

Prefer colocated store actions for ordinary Zustand updates. Choose a reducer when a defined set of action types, a pure transition function, or an existing Redux-style workflow makes the update logic clearer. The reducer receives the current state and an action, then returns the next state.

## Create a store with a reducer

1. Define the state and action union. Give each action a `type` and only the payload that action needs.
2. Return a new state from each reducer case. Return the existing state for actions the reducer does not handle.
3. Pass the reducer and initial state to `redux`, then wrap the result with [`create`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#create).
4. Select `count` and `dispatch` in the component. The sample mounts `Counter` into a page element, where it displays the count and changes it through dispatched actions.

```tsx
import { create } from 'zustand'
import { redux } from 'zustand/middleware'
import { createRoot } from 'react-dom/client'

type CounterState = {
  count: number
}

type CounterAction =
  | { type: 'counter/increment'; amount: number }
  | { type: 'counter/decrement'; amount: number }
  | { type: 'counter/reset' }

const initialState: CounterState = {
  count: 0,
}

function counterReducer(
  state: CounterState,
  action: CounterAction,
): CounterState {
  switch (action.type) {
    case 'counter/increment':
      return { ...state, count: state.count + action.amount }
    case 'counter/decrement':
      return { ...state, count: state.count - action.amount }
    case 'counter/reset':
      return initialState
    default:
      return state
  }
}

const useCounterStore = create(
  redux(counterReducer, initialState),
)

function Counter() {
  const count = useCounterStore((state) => state.count)
  const dispatch = useCounterStore((state) => state.dispatch)

  return (
    <section>
      <p>Count: {count}</p>
      <button
        type="button"
        onClick={() =>
          dispatch({ type: 'counter/decrement', amount: 1 })
        }
      >
        Decrement
      </button>
      <button
        type="button"
        onClick={() =>
          dispatch({ type: 'counter/increment', amount: 1 })
        }
      >
        Increment
      </button>
      <button
        type="button"
        onClick={() => dispatch({ type: 'counter/reset' })}
      >
        Reset
      </button>
    </section>
  )
}

document.body.innerHTML = '<div id="root"></div>'
const rootElement = document.getElementById('root')!

createRoot(rootElement).render(<Counter />)
```

The rendered count starts at `0`. Clicking **Increment** or **Decrement** dispatches an action, the reducer returns the next state, and the selected count updates. **Reset** returns the initial state.

## Use a vanilla store

When React is not the store's consumer, use [`createStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#createstore) with the same state creator. The returned store exposes `dispatch` on its state and API, so non-React code can dispatch actions directly.

```ts
import { createStore } from 'zustand/vanilla'
import { redux } from 'zustand/middleware'

type CounterState = {
  count: number
}

type CounterAction =
  | { type: 'counter/increment'; amount: number }
  | { type: 'counter/reset' }

type CounterStore = CounterState & {
  dispatch: (action: CounterAction) => CounterAction
}

const initialState: CounterState = { count: 0 }

function counterReducer(
  state: CounterState,
  action: CounterAction,
): CounterState {
  switch (action.type) {
    case 'counter/increment':
      return { ...state, count: state.count + action.amount }
    case 'counter/reset':
      return initialState
    default:
      return state
  }
}

const counterStore = createStore<CounterStore>()(
  redux(counterReducer, initialState),
)

const returnedAction = counterStore.dispatch({
  type: 'counter/increment',
  amount: 2,
})

console.log(returnedAction.type)
console.log(counterStore.getState().count)
```

`counterStore.dispatch()` returns the action it receives. `counterStore.getState()` then exposes the updated state to non-React code.

## Keep the reducer predictable

- Keep the reducer pure: calculate the next state from its two arguments and return it.
- Update state immutably. Spread the existing object when changing one field, and return the current `state` for an unhandled action.
- Keep action types discriminated with a string `type`; the middleware requires that shape.
- Remember that the reducer pattern is optional in Zustand. For smaller stores, define actions directly in the store and update through `set`.

For selector reference stability and render behaviour, see [Selectors and rendering](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/selectors-and-rendering). For immutable update details, see [Immutable state updates](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/immutable-state-updates).

Try the [live Zustand demo](https://zustand-demo.pmnd.rs/) to see a mounted store in the browser.

## Related

- [How Zustand works](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/how-zustand-works)
- [Typed store patterns](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/typed-store-patterns)
- [Create a vanilla store](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/create-a-vanilla-store)
