Skip to content
D
Documentation

Use Redux-style actions

how-to
2 min readUpdated

Handle store updates through a reducer and dispatched actions when a reducer-based action pattern fits your application.

Use this pattern when you want one reducer to describe how explicit action objects change a store. Zustand does not require reducers or dispatched actions; its recommended pattern is to keep actions with state and update through set or setState.

Create a reducer-backed store

The redux middleware takes a reducer and initial state, then returns a state creator with dispatch. Wrap it with create to get a React store hook.

  1. Define the state and action union. Each action has a string type and any data the reducer needs.
  2. Write a pure reducer that takes the current state and action and returns the next state.
  3. Pass the reducer and initial state to redux, then create a hook-backed store. The selected dispatch function accepts only the declared action union.
tsx
import { create } from 'zustand'
import { redux } from 'zustand/middleware'
import { createRoot } from 'react-dom/client'

type AlertsState = {
  alertsEnabled: boolean
}

type AlertsAction =
  | { type: 'alerts/enable' }
  | { type: 'alerts/disable' }

type AlertsStore = AlertsState & {
  dispatch: (action: AlertsAction) => AlertsAction
}

function alertsReducer(
  state: AlertsState,
  action: AlertsAction,
): AlertsState {
  switch (action.type) {
    case 'alerts/enable':
      return { alertsEnabled: true }
    case 'alerts/disable':
      return { alertsEnabled: false }
    default:
      return state
  }
}

const initialState: AlertsState = { alertsEnabled: false }

const useAlertsStore = create<AlertsStore>()(
  redux(alertsReducer, initialState),
)

function AlertsSetting() {
  const alertsEnabled = useAlertsStore((state) => state.alertsEnabled)
  const dispatch = useAlertsStore((state) => state.dispatch)

  return (
    <section>
      <p>Alerts: {alertsEnabled ? 'On' : 'Off'}</p>
      <button onClick={() => dispatch({ type: 'alerts/enable' })}>
        Enable alerts
      </button>
      <button onClick={() => dispatch({ type: 'alerts/disable' })}>
        Disable alerts
      </button>
    </section>
  )
}

const container = document.createElement('div')
document.body.append(container)
createRoot(container).render(<AlertsSetting />)
The initial Alerts: Off setting appears above the Enable alerts and Disable alerts buttons.

The mounted control starts at Alerts: Off. Clicking Enable alerts dispatches its action and changes the display to Alerts: On; Disable alerts dispatches the action that displays Alerts: Off.

Reducer and middleware inputs

redux has no configuration options here. It takes these two required inputs:

InputTypeDefaultEffect
reducer(state: T, action: A) => TNoneComputes the next state from the current state and an action. Keep it pure.
initialStateTNoneProvides the store's starting state; it cannot be a function.

The middleware returns a state creator. Its dispatch function is available in the store state and on the store API, and calling it returns the dispatched action.

Pitfalls

  • Reducers and dispatched actions are optional in Zustand. For the colocated-action pattern without a reducer, see Create and bind a React store.

See it running

Try the Zustand live demo.

Was this page helpful?