# Create a vanilla store

Create a framework-independent store, read it from React with `useStore`, and keep one scoped
instance stable for each provider.

## When to use a vanilla store

Use a vanilla store when store creation must not depend on React—for example, when non-React code
also reads or updates the state, or when each rendered scope needs its own instance. Install the
[`zustand`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand) package, which includes the [`zustand/vanilla`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand-vanilla)
entry point:

```bash
npm install zustand
```

## Create the store

Call [`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)
entry point when the store must be created independently of React; for the general store API and
update model, see [How Zustand works](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/how-zustand-works).

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

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

export const counterStore = createStore<CounterState>()((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}))

counterStore.getState().increment()
const currentCount = counterStore.getState().count

const unsubscribe = counterStore.subscribe((state) => {
  console.log(state.count)
})

unsubscribe()
```

These reads and subscriptions are the vanilla-store boundary described in [How Zustand works](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/how-zustand-works);
use `getInitialState()` when an external renderer needs an initial render before it subscribes.

For immutable updates and shallow merging, see [How Zustand works](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/how-zustand-works). Do not
assume that a middleware-modified `set` or `get` also changes a vanilla store's `setState` or
`getState`; those store methods do not include that middleware behavior.

## Bind the store to React

Pass the store and a selector to [`useStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#usestore). The component reads the
selected value and re-renders when that selection changes.

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

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

const counterStore = createStore<CounterState>()((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}))

function Counter() {
  const count = useStore(counterStore, (state) => state.count)

  return (
    <p>Count: {count}</p>
  )
}

function ExternalIncrement() {
  useEffect(() => {
    const button = document.getElementById('external-increment')
    if (!(button instanceof HTMLButtonElement)) return

    const increment = () => counterStore.getState().increment()
    button.addEventListener('click', increment)
    return () => button.removeEventListener('click', increment)
  }, [])

  return (
    <button id="external-increment" type="button">
      Increment from non-React code
    </button>
  )
}

export default function App() {
  return (
    <>
      <Counter />
      <ExternalIncrement />
    </>
  )
}
```

![The mounted counter shows Count: 0 and an external increment button.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/9ab651969e5ec9a42a204ba49d752463.png)

The mounted counter starts at `Count: 0`. Clicking the separate button calls the vanilla store
directly through `getState()`, while `useStore` updates the React display; the store remains usable
outside the component tree.

## Keep scoped instances stable

Create a store factory when a component tree needs independent state. Keep the instance in React
state with a lazy initializer; creating it during every render would replace the store and lose its
state and subscriptions.

```tsx
import { createContext, useContext, useState, type ReactNode } from 'react'
import { createStore } from 'zustand/vanilla'
import { useStore } from 'zustand'

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

function createCounterStore(initialCount: number) {
  return createStore<CounterState>()((set) => ({
    count: initialCount,
    increment: () => set((state) => ({ count: state.count + 1 })),
  }))
}

type CounterStore = ReturnType<typeof createCounterStore>
const CounterContext = createContext<CounterStore | null>(null)

function CounterProvider({
  initialCount,
  children,
}: {
  initialCount: number
  children: ReactNode
}) {
  const [store] = useState(() => createCounterStore(initialCount))
  return (
    <CounterContext.Provider value={store}>
      {children}
    </CounterContext.Provider>
  )
}

function useCounterStore<U>(selector: (state: CounterState) => U): U {
  const store = useContext(CounterContext)
  if (store === null) {
    throw new Error('useCounterStore must be used within CounterProvider')
  }
  return useStore(store, selector)
}

function Counter() {
  const count = useCounterStore((state) => state.count)
  const increment = useCounterStore((state) => state.increment)
  return (
    <button type="button" onClick={increment}>
      Count: {count}
    </button>
  )
}

export default function App() {
  return (
    <div>
      <CounterProvider initialCount={2}>
        <Counter />
      </CounterProvider>
      <CounterProvider initialCount={10}>
        <Counter />
      </CounterProvider>
    </div>
  )
}
```

![Two buttons show independent counts, Count: 2 and Count: 10.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/424bfa87a15fe5d592cc96db23e9ea42.png)

The page shows two buttons with independent counts, `Count: 2` and `Count: 10`. Each provider
creates its store once, so clicking one button changes only its own count. The lazy `useState`
initializer also keeps the store identity stable across re-renders.

For server-rendered applications, create stores per request rather than sharing a global module
store; see [Handle server-rendered stores](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/handle-server-rendered-stores).

## API summary

| API | Use it for |
| --- | --- |
| `createStore` | Create a store without a framework binding. |
| `useStore` | Select state from a vanilla store in a React component. |
| `StoreApi` | Type the store instance that provides `setState`, `getState`, `getInitialState`, and `subscribe`. |

See [Vanilla and scoped stores](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/vanilla-and-scoped-stores) for the store model and [Typed
store patterns](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/typed-store-patterns) for reusable TypeScript shapes.
