Learning Zustand - Zustand Outside React (Vanilla Store)
Episode 17 of 23

Learning Zustand - Zustand Outside React (Vanilla Store)

This episode covers zustand/vanilla: creating stores without hooks with createStore, using getState and subscribe in non-React code like Node services and web workers, then connecting the vanilla store back to React with useStore. One store can be used from both worlds with a single source of truth.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

One of Zustand's architecture advantages is a core that doesn't depend on React. Episode 17 covers zustand/vanilla: creating stores without hooks, using getState and subscribe in non-React code like Node services or web workers, then connecting the vanilla store to React components with useStore.

You'll see why a single store can be used from two different worlds with one source of truth.

createStore Without Hooks

The zustand/vanilla module exposes createStore, which returns a store API instead of a hook:

JSVanilla store without React
import { createStore } from 'zustand/vanilla'
 
interface CounterState {
  count: number
  increment: () => void
}
 
export const counterStore = createStore<CounterState>()((set) => ({
  count: 0,
  increment: () => set((s) => ({ count: s.count + 1 })),
}))
 
counterStore.getState()
counterStore.subscribe((s) => console.log('count:', s.count))

createStore from zustand/vanilla returns an object with getState, setState, subscribe, and getInitialState. There's no JSX, no hooks, and no React inside it — purely a state container you can import anywhere.

Vanilla Stores Outside React

Node Service

Because it doesn't use React, a vanilla store can be used in a Node service that processes queues or events. Here's a listener responding to state changes from outside:

JSListener in a Node service
import { counterStore } from './counter-store.js'
 
const unsubscribe = counterStore.subscribe((state) => {
  if (state.count > 10) {
    notifyAdmin('counter melewati batas')
  }
})
 
process.on('SIGINT', unsubscribe)

counterStore.subscribe(listener) calls the listener with the full state on every change. This is a simple event bus: business logic reads state and makes decisions without needing a UI.

Web Worker

In a web worker, the state held by the worker can't automatically share with the main thread. A vanilla store inside the worker becomes the local source of truth:

JSStore inside a web worker
import { createStore } from 'zustand/vanilla'
 
const workerStore = createStore((set) => ({
  total: 0,
  add: (n) => set((s) => ({ total: s.total + n })),
}))
 
self.onmessage = (event) => {
  workerStore.getState().add(event.data)
  self.postMessage({ total: workerStore.getState().total })
}

workerStore.getState().add(event.data) mutates state in the worker, then the result is sent back to the main thread. A vanilla store removes the need to manage a mutable global manually in the worker.

Connecting a Vanilla Store to React

A vanilla store can be used directly in React with useStore from zustand. Components subscribe to the same store so selective re-rendering still works:

JSuseStore connects the vanilla store
import { useStore } from 'zustand'
import { counterStore } from './counter-store'
 
export function Counter() {
  const count = useStore(counterStore, (s) => s.count)
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => counterStore.getState().increment()}>
        Tambah
      </button>
    </div>
  )
}

useStore(counterStore, (s) => s.count) takes the vanilla store and a selector like a regular hook. The result: state logic in a separate file that can be tested without React, and a React UI that subscribes with full performance — one store, two consumers.

Closing

Episode 17 opens Zustand's door out of React: createStore produces a vanilla store used in Node services and web workers via getState and subscribe, then connected back to React with useStore without rewriting the store.

Key takeaways:

  • zustand/vanilla provides createStore without a React dependency.
  • A vanilla store exposes getState, setState, subscribe, and getInitialState.
  • Node services and web workers can use a vanilla store as an event bus.
  • Subscribing outside React enables business logic without a UI.
  • useStore connects a vanilla store to React components.
  • One store can be used by React and non-React with the same source of truth.

In the next episode we will discuss testing stores and middleware — writing unit tests with Vitest for set and get state, testing async actions and the persist middleware with mocked storage, and component testing with React Testing Library.

Learning Zustand - Zustand Outside React (Vanilla Store) | Learning Zustand