# How Zustand works

Zustand connects a store, the actions that update it, and selectors that let React components subscribe to only the state they use.

Install Zustand and React in a React project:

```bash
npm install zustand react
```

```mermaid
flowchart LR
  A["create()"] --> B["Store: state + actions"]
  B --> C["Selector"]
  C --> D["React component"]
  B --> E["setState()"]
  E --> B
  F["createStore()"] --> G["Vanilla StoreApi"]
  G --> H["useStore()"]
  H --> D
```

## Updates are immutable and merge shallowly

Use the `set` function supplied to the initializer, or the store's `setState`, for updates. An object update is shallowly merged into the current state; a function update receives the current state. Nested objects need their own spread so that the untouched nested properties remain present.

```tsx
import { create } from 'zustand'

type ProfileStore = {
  profile: {
    name: string
    settings: {
      compact: boolean
    }
  }
  rename: (name: string) => void
  setCompact: (compact: boolean) => void
}

const useProfile = create<ProfileStore>((set) => ({
  profile: {
    name: 'Ada',
    settings: { compact: false },
  },
  rename: (name) => set((state) => ({ profile: { ...state.profile, name } })),
  setCompact: (compact) =>
    set((state) => ({
      profile: {
        ...state.profile,
        settings: { ...state.profile.settings, compact },
      },
    })),
}))

export function Profile() {
  const profile = useProfile((state) => state.profile)
  const rename = useProfile((state) => state.rename)

  return (
    <label>
      Name
      <input value={profile.name} onChange={(event) => rename(event.target.value)} />
    </label>
  )
}
```

Typing in the input replaces only `profile.name`; the nested `settings` object remains part of the state because the update merges each level explicitly. Passing the replace flag to `setState` instead replaces the complete state model, including actions, so use it only when that is the intended result. The underlying store applies the merge or replacement and notifies listeners only when the next state is not `Object.is`-equal to the current state.

## Selectors and equality control rendering

A selector narrows what a component reads. The React binding compares the selected result with `Object.is`, which makes atomic selections such as a number or function efficient. A selector that constructs a new object or array produces a new reference; wrap that selector with [`useShallow`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand-react-shallow#useshallow) when shallow-equal results should reuse the previous reference.

```tsx
import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'

type BasketStore = {
  apples: number
  oranges: number
  addApple: () => void
}

const useBasket = create<BasketStore>((set) => ({
  apples: 0,
  oranges: 0,
  addApple: () => set((state) => ({ apples: state.apples + 1 })),
}))

export function BasketSummary() {
  const fruit = useBasket(
    useShallow((state) => ({ apples: state.apples, oranges: state.oranges })),
  )

  return <p>{fruit.apples} apples, {fruit.oranges} oranges</p>
}
```

The component renders the two counts as one selected object. `useShallow` returns the previous object when its selected properties are shallow-equal. For a single primitive, select it directly instead of constructing an object.

## Actions stay with the state

Zustand recommends a single store for an application's global state, with actions defined directly on that store. An action can be asynchronous: wait for the work, then call `set`. Use `get` in the initializer when an action needs the current state outside a functional update. Reducer-style actions remain an optional pattern rather than the store's required layer.

The initializer receives a typed [`StateCreator`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#statecreator), whose arguments are the update function, the getter, and the store API. The store returned by [`create`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#create) is a [`UseBoundStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#useboundstore): it is callable as a hook and also exposes the store API. [`ExtractState`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#extractstate) obtains the state type from an API; [`StoreApi`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#storeapi) describes `setState`, `getState`, `getInitialState`, and `subscribe`.

The generic type plumbing for middleware uses [`Mutate`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#mutate), [`StoreMutatorIdentifier`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#storemutatoridentifier), and [`StoreMutators`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#storemutators). These types describe how a store API is changed by mutators; use them when extending middleware typings, not as a replacement for the store hook.

## A vanilla store separates creation from React

Use [`createStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#createstore) from the [`zustand/vanilla`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand-vanilla) package when the store must exist without React. It returns a vanilla API rather than a hook. The API reads current state, updates it, exposes the initial state, and lets non-React code subscribe.

```tsx
import { useStore } from 'zustand'
import { createStore } from 'zustand/vanilla'

type ClockStore = {
  label: string
  setLabel: (label: string) => void
}

const clockStore = createStore<ClockStore>((set) => ({
  label: 'Ready',
  setLabel: (label) => set({ label }),
}))

export function Clock() {
  const label = useStore(clockStore, (state) => state.label)
  const setLabel = useStore(clockStore, (state) => state.setLabel)

  return <button onClick={() => setLabel('Started')}>{label}</button>
}
```

The button initially shows `Ready`; clicking it updates the vanilla store and the bound component shows `Started`. [`useStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#usestore) subscribes the component to that store without turning the store itself into a React hook. This separation also supports scoped or per-request stores when module-global state is unsafe in server-rendered applications. Do not assume middleware that changes the initializer's `set` or `get` also changes a vanilla store's direct `getState` and `setState`; that boundary is covered in [Create a vanilla store](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/create-a-vanilla-store).

## Where to go next

- [Build a React store](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/build-a-react-store) for a complete application store.
- [Immutable state updates](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/immutable-state-updates) for nested update patterns.
- [Selectors and rendering](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/selectors-and-rendering) for selector stability and equality.
- [Create a vanilla store](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/create-a-vanilla-store) for non-React and scoped stores.
- [Handle server-rendered stores](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/handle-server-rendered-stores) for per-request state and server-rendered applications.
