# Initialize scoped stores

Use a vanilla store with React context when you need dependency injection or initial state from component props. Each provider supplies an independent store; consumers beneath that provider share it.

The store exists independently of React: [`createStore`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#createstore) returns the store API, and [`useStore`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#usestore) connects a React component to it. Context carries the store instance, not a bound hook.

The example below renders two bear counters initialized to 2 and 5. Clicking **Add bear** changes only the counter in that provider's scope. Run it in a browser React TypeScript project with Zustand installed:

```bash
npm install zustand
```

## 1. Create a store factory

Accept initial props in a factory rather than creating one global instance. Each call returns a new [`StoreApi`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#storeapi) with its own state and listeners. Merge the initial props over your application defaults, then define the actions.

```ts title="bear-store.ts"
import { createStore, type StoreApi } from 'zustand'

export type BearProps = {
  bears: number
}

export type BearState = BearProps & {
  addBear: () => void
}

export function createBearStore(
  initProps: Partial<BearProps> = {},
): StoreApi<BearState> {
  const defaults: BearProps = { bears: 0 }

  return createStore<BearState>()((set) => ({
    ...defaults,
    ...initProps,
    addBear: () => set((state) => ({ bears: state.bears + 1 })),
  }))
}
```

The functional update reads the previous count and returns the next count. The merging update preserves `addBear` in the store.

## 2. Provide a stable instance and a typed hook

Keep the store in a lazy `useState` initializer, following the provider-wrapper pattern. The provider retains that instance across re-renders instead of creating a new store on every render.

The consumer hook accepts a selector and returns its inferred result type. It reads the context, rejects a missing provider, and delegates the subscription to `useStore`.

```tsx title="bear-context.tsx"
import {
  createContext,
  useContext,
  useState,
  type PropsWithChildren,
} from 'react'
import { useStore, type StoreApi } from 'zustand'
import { createBearStore, type BearProps, type BearState } from './bear-store'

const BearContext = createContext<StoreApi<BearState> | null>(null)

type BearProviderProps = PropsWithChildren<Partial<BearProps>>

export function BearProvider({ children, bears }: BearProviderProps) {
  const [store] = useState(() => createBearStore({ bears: bears ?? 0 }))

  return (
    <BearContext.Provider value={store}>
      {children}
    </BearContext.Provider>
  )
}

export function useBearContext<T>(selector: (state: BearState) => T): T {
  const store = useContext(BearContext)

  if (store === null) {
    throw new Error('Missing BearProvider in the tree')
  }

  return useStore(store, selector)
}

function BearCount() {
  const bears = useBearContext((state) => state.bears)
  const addBear = useBearContext((state) => state.addBear)

  return (
    <section>
      <p>{bears} Bears.</p>
      <button type="button" onClick={addBear}>Add bear</button>
    </section>
  )
}

export default function BearScopeExample() {
  return (
    <main style={{ display: 'flex', gap: 32, minHeight: 160 }}>
      <BearProvider bears={2}><BearCount /></BearProvider>
      <BearProvider bears={5}><BearCount /></BearProvider>
    </main>
  )
}
```

![The two independent scopes start with 2 Bears and 5 Bears, each with its own Add bear button.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/20043d062e53118ca573af347f2a9733.png)

## 3. Render independent scopes

Select the count and action separately. Both consumers use the same hook, but each reads the store supplied by its nearest provider.

```tsx title="App.tsx"
import { BearProvider, useBearContext } from './bear-context'

function BearCounter({ label }: { label: string }) {
  const bears = useBearContext((state) => state.bears)
  const addBear = useBearContext((state) => state.addBear)

  return (
    <section>
      <h2>{label}</h2>
      <p>{bears} Bears.</p>
      <button type="button" onClick={addBear}>Add bear</button>
    </section>
  )
}

export default function App() {
  return (
    <main style={{ display: 'flex', gap: 32, minHeight: 160 }}>
      <BearProvider bears={2}>
        <BearCounter label="First scope" />
      </BearProvider>
      <BearProvider bears={5}>
        <BearCounter label="Second scope" />
      </BearProvider>
    </main>
  )
}
```

Mount `App` in your project's root element. This entry point expects an element with `id="root"` in your HTML:

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

const container = document.getElementById('root')
if (container === null) {
  throw new Error('Missing root element')
}

createRoot(container).render(<App />)
```

You see **First scope** with `2 Bears.` and **Second scope** with `5 Bears.` Click the first scope's button: its count becomes 3, while the second stays at 5. Put additional consumers inside either provider to share that provider's store.

## Inputs that matter

`useStore` takes the store instance and an optional selector; it has no options object.

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| Store argument | `StoreApi<BearState>` in this example | Required | Chooses the instance to subscribe to. |
| Selector argument | `(state: BearState) => T` in the consumer hook | Identity selector in `useStore`; required by `useBearContext` | Returns the value the component reads. |

The example's application-defined `bears` prop supplies the initial count and defaults to 0 when omitted. It is not a Zustand configuration option.

## Pitfalls

- Treat provider props as initialization, not synchronization. The lazy initializer captures the initial props; later prop changes do not update the retained store. Use a store action for subsequent updates.
- Keep each consumer under `BearProvider`. The custom hook throws when the context contains `null`.
- Do not create the store directly in the provider's render body. Keep the lazy initializer so re-renders retain the current count.
- For selectors that derive objects or arrays, see [Selectors and subscriptions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/selectors-and-subscriptions).
- For server rendering and request-local initialization, see [Next.js store setup](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/nextjs-store-setup).

## Live demo

Try the [Zustand live demo](https://zustand-demo.pmnd.rs/) to see a counter driven by a store action. The scoped example above adds context and a store factory so separate counters do not share one global store.

## Related

- [Store scope and lifecycle](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/store-scope-and-lifecycle)
- [Update state with actions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/update-state-with-actions)
- [Test stores and components](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/test-stores-and-components)
