# Subscribe to store state outside components

Use a vanilla store when code outside React needs to read current state, update it, or react to
changes. Bind that same store to React with [`useStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#usestore), and use a selected
subscription when an external listener only needs part of the state.

## Create a store for non-React code

For [`createStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#createstore), [`StoreApi`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#storeapi), and their
`getState()`, `setState()`, `getInitialState()`, and `subscribe()` methods, see [Typed store
patterns](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/typed-store-patterns); this page focuses on using them from non-React code.

The subscription can update a browser element directly, so an external listener changes the page
without a React render:

```ts title="counter-store.ts"
import { createStore } from 'zustand/vanilla'

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

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

const output = document.createElement('output')
const button = document.createElement('button')
button.type = 'button'
button.textContent = 'Increment'

const render: Parameters<typeof counterStore.subscribe>[0] = (state) => {
  output.textContent = `Count: ${state.count}`
}

render(counterStore.getInitialState(), counterStore.getInitialState())
counterStore.subscribe(render)
button.addEventListener('click', () => counterStore.getState().increment())
document.body.append(output, button)
```

Read and update the store from any code that imports it. A subscription callback receives the new
state and the previous state, and the returned function removes that listener.

```ts title="counter-effects.ts"
import { counterStore } from './counter-store'

const initialCount = counterStore.getInitialState().count
const currentCount = counterStore.getState().count

const unsubscribe = counterStore.subscribe((state, previousState) => {
  console.log('count changed', previousState.count, state.count)
})

counterStore.getState().increment()
counterStore.setState({ count: currentCount + 2 })

console.log('initial count', initialCount)
unsubscribe()
```

The first read gets the store's initial model; later `getState()` calls get the current model. The
action updates state through the store, and `setState()` performs the update without requiring a
component.

## Subscribe to a selected value

Wrap the state creator with [`subscribeWithSelector`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand-middleware#subscribewithselector)
when an external listener needs a slice rather than every state update.

```ts title="temperature-store.ts"
import { createStore } from 'zustand/vanilla'
import { subscribeWithSelector } from 'zustand/middleware'

type TemperatureStore = {
  celsius: number
  label: string
  setTemperature: (celsius: number) => void
}

export const temperatureStore = createStore<TemperatureStore>()(
  subscribeWithSelector((set) => ({
    celsius: 20,
    label: 'room',
    setTemperature: (celsius) => set({ celsius }),
  })),
)

const unsubscribe = temperatureStore.subscribe(
  (state) => state.celsius,
  (celsius, previousCelsius) => {
    console.log('temperature changed', previousCelsius, celsius)
  },
  { fireImmediately: true },
)

temperatureStore.setState({ label: 'office' })
temperatureStore.getState().setTemperature(21)
unsubscribe()
```

The listener fires immediately with the selected value and then fires when `celsius` changes. The
`label` update does not call this listener because the selected value is unchanged. The selector
subscription compares selected values with `Object.is` unless you provide `equalityFn` in its
options.

## Use the same store in React

Pass the vanilla store to `useStore` and select the value or action the component needs. React
subscribes the component to that selection; the component re-renders when the selected value
changes.

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

import { temperatureStore } from './temperature-store'

export function TemperaturePanel() {
  const celsius = useStore(temperatureStore, (state) => state.celsius)
  const setTemperature = useStore(
    temperatureStore,
    (state) => state.setTemperature,
  )

  return (
    <section>
      <p>Temperature: {celsius}°C</p>
      <button type="button" onClick={() => setTemperature(celsius + 1)}>
        Increase
      </button>
    </section>
  )
}

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

The panel displays the current temperature. Clicking **Increase** updates the shared vanilla store,
and the selected `celsius` value causes the panel to render the new temperature.

## Handle high-frequency changes without re-rendering

For frequently changing state, subscribe in an effect and update a DOM ref directly. The
subscription changes the rendered element without making the component render for every pointer
event.

```tsx title="PointerPreview.tsx"
import { useEffect, useRef } from 'react'
import { createStore } from 'zustand/vanilla'

type PointerStore = {
  x: number
  y: number
  setPosition: (x: number, y: number) => void
}

const pointerStore = createStore<PointerStore>()((set) => ({
  x: 0,
  y: 0,
  setPosition: (x, y) => set({ x, y }),
}))

export function PointerPreview() {
  const dotRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const unsubscribe = pointerStore.subscribe((state) => {
      const dot = dotRef.current
      if (dot) {
        dot.style.transform = `translate(${state.x}px, ${state.y}px)`
      }
    })

    return unsubscribe
  }, [])

  return (
    <div
      onPointerMove={(event) =>
        pointerStore.getState().setPosition(event.clientX, event.clientY)
      }
      style={{ position: 'relative', height: 240, width: 400 }}
    >
      <div
        ref={dotRef}
        style={{
          background: 'crimson',
          borderRadius: '50%',
          height: 16,
          position: 'absolute',
          width: 16,
        }}
      />
    </div>
  )
}
```

The red dot moves with the pointer while the pointer is over the 400-by-240 pixel area. The effect
subscribes when the component mounts and removes the listener when it unmounts, so the component
does not retain a subscription after teardown.

## Options that matter

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `equalityFn` | `(a, b) => boolean` | `Object.is` | Decides whether a selected-value listener runs. |
| `fireImmediately` | `boolean` | `false` | Calls a selected-value listener once with its current value when the subscription is created. |

## Pitfalls

- Keep the unsubscribe function and call it when the owner of the subscription is disposed. In a
  React component, return it from the `useEffect` cleanup as shown above.
- A selector that creates a new reference on every render can cause update loops in v5. Return a
  stable reference or follow the shallow-selection guidance in [Selectors and rendering](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/selectors-and-rendering).
- Do not read or write a global store from React Server Components. Create stores per request for
  server-rendered applications; see [Handle server-rendered stores](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/handle-server-rendered-stores).

## Related

- [Create a vanilla store](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/create-a-vanilla-store)
- [Selectors and rendering](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/selectors-and-rendering)
- [Build a React store](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/build-a-react-store)
