# How Zustand works

Zustand is a state-management library that keeps state and actions in a store and connects React components to it through selector subscriptions.

## Your store is a hook

Pass a state creator to [`create`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#create). It returns a hook bound to the store, with store methods attached. Your state can contain primitives, objects, and functions; actions are functions that update that state. Use the hook in components without a provider.

This browser example renders a count starting at `0`. Click **+1** to increment it. The counter selects the count and the action separately, following the React starter's pattern.

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

type CounterState = {
  count: number
  inc: () => void
}

const useCounterStore = create<CounterState>()((set) => ({
  count: 0,
  inc: () => set((state) => ({ count: state.count + 1 })),
}))

function Counter() {
  const count = useCounterStore((state) => state.count)
  const inc = useCounterStore((state) => state.inc)
  return <div><span>{count}</span> <button onClick={inc}>+1</button></div>
}

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

The hook connects your component to an underlying store. An action updates the store; the subscription lets React read the selected value again.

```mermaid
flowchart LR
  C["Component"] -->|calls| A["Store action"]
  A -->|set| S["Store state"]
  S -->|notifies| H["Hook subscription"]
  H -->|selector output| C
```

See the [live counter demo](https://zustand-demo.pmnd.rs/) for this state-and-action pattern in a rendered application. For project setup, start with the [React quick start](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/react-quick-start).

## State is immutable; updates merge at one level

Use `set` inside the state creator's actions, or `setState()` on the store, to update state and notify listeners. When the next value depends on the previous state, pass a function to `set`. For object state, the default update shallowly merges the returned fields into the existing state, so the counter action above keeps `inc` without spreading the entire state.

Copy nested objects explicitly. Here **+1** updates `nested.count` while keeping `nested.label`; the screen starts at `Visits: 0`.

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

type VisitState = {
  nested: { count: number; label: string }
  inc: () => void
}

const useVisitStore = create<VisitState>()((set) => ({
  nested: { count: 0, label: 'Visits' },
  inc: () => set((state) => ({
    nested: { ...state.nested, count: state.nested.count + 1 },
  })),
}))

function Visits() {
  const nested = useVisitStore((state) => state.nested)
  const inc = useVisitStore((state) => state.inc)
  return (
    <div>
      <span>{nested.label}: {nested.count}</span>
      <button onClick={inc}>+1</button>
    </div>
  )
}

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

For deeper structures, see [Update nested state](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/update-nested-state). [State and actions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/state-and-actions) covers object, `Map`, and `Set` updates; [Type stores and middleware](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/type-stores-and-middleware) covers replacement updates.

## Selectors define the subscription

Select what your component needs instead of reading the whole store. React compares the selected snapshot with `Object.is`: a changed selector output triggers a store-driven re-render.

For computed outputs, [`useShallow`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand-react-shallow#useshallow) returns a selector that preserves the previous output reference when the next output is shallowly equal. This makes an array or object selection stable when its contents have not changed.

Install React explicitly: it is an optional peer dependency, so installing Zustand alone does not add it.

```bash
npm install zustand react react-dom
```

This example follows the bear-meals example: it renders the bear names and papa bear's meal. Click **Order pizza** to change the meal. The names remain the same, and `BearNames` does not re-render because of that store update.

```tsx title="main.tsx"
import { createRoot } from 'react-dom/client'
import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'

const useMeals = create(() => ({
  papaBear: 'large porridge-pot',
  mamaBear: 'middle-size porridge pot',
  littleBear: 'A little, small, wee pot',
}))

function BearNames() {
  const names = useMeals(useShallow((state) => Object.keys(state)))
  return <p>{names.join(', ')}</p>
}

function Meal() {
  const meal = useMeals((state) => state.papaBear)
  return (
    <div>
      <p>Papa bear's meal: {meal}</p>
      <button onClick={() => useMeals.setState({ papaBear: 'a large pizza' })}>
        Order pizza
      </button>
    </div>
  )
}

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

See [Selectors and subscriptions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/selectors-and-subscriptions) for selector stability in v5, and [Prevent unnecessary rerenders](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/prevent-unnecessary-rerenders) for equality choices.

## The store exists independently of React

[`createStore`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#createstore) returns a standalone store, not a hook. Import it from `zustand/vanilla` when you need the core without a React dependency. [`useStore`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#usestore) connects a React component to that store and returns its selected state.

The store exposes the [`StoreApi`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#storeapi) methods:

| Method | What you get |
| --- | --- |
| `getState()` | The current state, without subscribing. |
| `setState()` | A state update, with the same merging behavior as `set`. It returns `void`. |
| `subscribe(listener)` | A subscription receiving current and previous state on updates. It returns an unsubscribe function. |
| `getInitialState()` | The state returned by the initial state creator. |

In this browser example, the button updates the vanilla store through `setState()`. The mounted component observes that same store through `useStore`, so the displayed count increases.

```tsx title="main.tsx"
import { createRoot } from 'react-dom/client'
import { useStore } from 'zustand'
import { createStore } from 'zustand/vanilla'

const counterStore = createStore(() => ({ count: 0 }))

function Counter() {
  const count = useStore(counterStore, (state) => state.count)
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => counterStore.setState((state) => ({
        count: state.count + 1,
      }))}>+1</button>
    </div>
  )
}

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

For dependency injection or initialization from component props, put a vanilla store in React context and select from it with `useStore`, rather than passing the bound hook as a context value. See [Initialize scoped stores](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/initialize-scoped-stores) for that pattern, [Subscribe outside React](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/subscribe-outside-react) for listener lifetimes, and [Next.js store setup](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/nextjs-store-setup) for server-side scope.

## Compose actions and slices into one store

The recommended Flux-inspired pattern keeps application global state in a single store with colocated actions. You call those functions directly; dispatched actions and reducers are not required. Slices keep that store modular without creating separate subscriptions to separate stores.

Type a slice creator with [`StateCreator`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#statecreator): its first type argument describes the combined state, and its fourth describes the slice it returns. Pass the same `set`, `get`, and store arguments to every creator. Each slice can then update the combined state or call another slice's actions through `get()`.

This example renders `0 bears, 0 fishes`. **Add both** calls the bear and fish actions through the shared slice, increasing both counts.

```tsx title="main.tsx"
import { createRoot } from 'react-dom/client'
import { create, type StateCreator } from 'zustand'

type BearSlice = { bears: number; addBear: () => void }
type FishSlice = { fishes: number; addFish: () => void }
type SharedSlice = { addBoth: () => void }
type ZooState = BearSlice & FishSlice & SharedSlice

const createBearSlice: StateCreator<ZooState, [], [], BearSlice> = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
})

const createFishSlice: StateCreator<ZooState, [], [], FishSlice> = (set) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})

const createSharedSlice: StateCreator<ZooState, [], [], SharedSlice> = (_set, get) => ({
  addBoth: () => {
    get().addBear()
    get().addFish()
  },
})

const useZooStore = create<ZooState>()((...args) => ({
  ...createBearSlice(...args),
  ...createFishSlice(...args),
  ...createSharedSlice(...args),
}))

function Zoo() {
  const bears = useZooStore((state) => state.bears)
  const fishes = useZooStore((state) => state.fishes)
  const addBoth = useZooStore((state) => state.addBoth)
  return (
    <div>
      <p>{bears} bears, {fishes} fishes</p>
      <button onClick={addBoth}>Add both</button>
    </div>
  )
}

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

Continue with [Split a store into slices](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/split-a-store-into-slices) for composition, or [Type stores and middleware](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/type-stores-and-middleware) for middleware placement and slice typing.
