# State and actions

A state creator defines a store's initial data and the actions that update it, keeping values and update functions together.

Pass that function to [`create`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#create) to get a store bound to a React hook. Your store can contain primitives, objects, and functions. Select the data or action each component needs; no provider is required.

## How updates flow

Keep application global state in a single store and colocate its actions. This Flux-inspired pattern does not require dispatched actions or reducers: an action is a function that calls `set`. For a larger store, [compose slices](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/split-a-store-into-slices) without changing that model.

```mermaid
flowchart LR
  A["State creator: data and actions"] --> B["create()"]
  B --> C["Bound store hook"]
  C --> D["Component selects data and actions"]
  D --> E["User calls an action"]
  E --> F["set() computes the next state"]
  F --> G["Merge or replace, then notify listeners"]
  G --> D
```

The creator receives `set`, `get`, and the store API. `set` updates state, while `get` reads the current state when an action runs. See [Reset store state](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/reset-store-state) for initialization constraints.

Use a functional update when a value depends on previous state. Zustand calls the updater with the current state, computes the next state, and notifies listeners if that result differs from the current state by `Object.is`.

## Data and actions in code

This browser example displays a heading and a nested counter. Clicking **+1** increases the count while preserving the nested label. Editing the heading changes only that top-level field. Clicking **Use default heading** updates the same mounted store through `setState` rather than a colocated action.

Type the creator with [`StateCreator`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#statecreator). The following two files belong in a React TypeScript project with `zustand` installed and a `<div id="root"></div>` in its HTML.

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

type CounterState = {
  heading: string
  nested: { count: number; label: string }
}

type CounterActions = {
  inc: () => void
  setHeading: (heading: string) => void
}

type CounterStore = CounterState & CounterActions

const counterCreator: StateCreator<CounterStore> = (set) => ({
  heading: 'Daily count',
  nested: { count: 0, label: 'Clicks' },
  inc: () =>
    set((state) => ({
      nested: { ...state.nested, count: state.nested.count + 1 },
    })),
  setHeading: (heading) => set({ heading }),
})

export const useCounterStore = create<CounterStore>()(counterCreator)
```

```tsx title="main.tsx"
import { createRoot } from 'react-dom/client'
import { useCounterStore } from './store'

function Counter() {
  const heading = useCounterStore((state) => state.heading)
  const nested = useCounterStore((state) => state.nested)
  const inc = useCounterStore((state) => state.inc)
  const setHeading = useCounterStore((state) => state.setHeading)

  return (
    <main style={{ minHeight: 400 }}>
      <h1>{heading}</h1>
      <label>
        Heading
        <input
          value={heading}
          onChange={(event) => setHeading(event.currentTarget.value)}
        />
      </label>
      <p>{nested.label}: {nested.count}</p>
      <button onClick={inc}>+1</button>
      <button
        onClick={() => useCounterStore.setState({ heading: 'Daily count' })}
      >
        Use default heading
      </button>
    </main>
  )
}

createRoot(document.getElementById('root')!).render(<Counter />)
```

The hook returns each selector's result. The same bound hook also exposes the [`StoreApi`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#storeapi) methods: `setState`, `getState`, `getInitialState`, and `subscribe`. Its combined hook-and-API type is [`UseBoundStore`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#useboundstore).

Inside the creator, `set` is the store's `setState` function. Both update paths therefore use the same merging and notification behavior, and both return `void`. You can also put actions at module level and call `useCounterStore.setState` there; selecting an action through the hook is not required for that pattern.

## Merge versus replace

For object state, `set({ heading })` shallowly merges the update with the current store. You do not need to spread the entire state: fields omitted from the update remain unchanged, including the action functions.

| Update | Result |
| --- | --- |
| Object update with `replace` omitted | Merge at the top level. |
| Update with `replace: false` as the second argument | Explicitly request merging. |
| Update with `true` as the second argument | Replace instead of merging. |
| Non-object or `null` update with `replace` omitted | Replace the state. |

The replacement flag is a positional boolean, not an options object. See [Type stores and middleware](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/type-stores-and-middleware) for complete-state replacement and its typing requirements.

## Why nested updates need new references

`set` merges only one level. It does not merge fields inside `nested`. In `inc`, the spread copies the nested object and preserves `label`, then overwrites `count`. For deeper objects, copy every level along the changed path. See [Update nested state](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/update-nested-state) for deeper updates and immutable-update helpers.

Treat `Map` and `Set` the same way: create new instances for updates, using `new Map(state.map).set(key, value)` or `new Set(state.set).add(item)`. Mutating an existing collection preserves its reference, so a component selecting that collection does not get the expected re-render. A new top-level update alone does not fix an unchanged selected collection reference.

For subscription details, continue with [Selectors and subscriptions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/selectors-and-subscriptions). To see nested updates running, open the [live nested-state demo](https://stackblitz.com/edit/vitejs-vite-j6bjdygu).
