Skip to main content
XState Store is a simple, framework-agnostic state management library that provides reactive stores with a minimal API. It’s designed for applications that need structured state management without the complexity of full state machines.

What is XState Store?

XState Store provides:
  • Simple stores - Manage state with a context object and event-based transitions
  • Type-safe events - Full TypeScript support with inferred event types
  • Reactive subscriptions - Subscribe to state changes and derived values
  • Framework integrations - First-class support for React, Solid, and Vue
  • Atoms - Fine-grained reactive primitives for computed values
  • XState interop - Use stores as actors in XState machines

Installation

npm install @xstate/store
For framework-specific hooks:
# React
import { useSelector } from '@xstate/store/react';

# Solid
import { useSelector } from '@xstate/store/solid';

Quick Example

import { createStore } from '@xstate/store';

const store = createStore({
  context: { count: 0, name: 'Ada' },
  on: {
    inc: (context, event: { by: number }) => ({
      ...context,
      count: context.count + event.by
    }),
    updateName: (context, event: { name: string }) => ({
      ...context,
      name: event.name
    })
  }
});

// Subscribe to changes
store.subscribe((snapshot) => {
  console.log(snapshot.context);
});

// Send events
store.send({ type: 'inc', by: 5 });
// Logs: { count: 5, name: 'Ada' }

// Or use the trigger helper
store.trigger.inc({ by: 3 });
// Logs: { count: 8, name: 'Ada' }

Core Concepts

Context

The context is your store’s state. It’s a plain object that holds all your application data:
const store = createStore({
  context: {
    user: { id: 1, name: 'Alice' },
    settings: { theme: 'dark' },
    items: []
  },
  on: { /* ... */ }
});

Events and Transitions

Events trigger transitions that update context. Each event handler receives the current context and event payload:
const store = createStore({
  context: { items: [] as string[] },
  on: {
    addItem: (context, event: { item: string }) => ({
      ...context,
      items: [...context.items, event.item]
    }),
    removeItem: (context, event: { index: number }) => ({
      ...context,
      items: context.items.filter((_, i) => i !== event.index)
    })
  }
});

Snapshots

Calling store.getSnapshot() or store.get() returns a snapshot with:
const snapshot = store.get();
// {
//   status: 'active',
//   context: { /* your state */ },
//   output: undefined,
//   error: undefined
// }
The snapshot structure matches XState’s snapshot format, making stores compatible with XState actors.

When to Use XState Store

Use XState Store when:

  • You need simple, event-driven state management
  • Your state updates follow predictable patterns
  • You want type-safe state and events
  • You need fine-grained reactivity with selectors
  • You’re building a new app without complex workflows

Use XState (state machines) when:

  • You have complex state transitions with guards and conditions
  • Your logic involves sequential workflows or multi-step processes
  • You need state charts for visualization
  • You require advanced features like actors, spawning, and hierarchical states
  • Your domain has many possible states and transitions between them
You can use both! XState Store can be used as an actor in XState machines with fromStore().

Store vs Redux

If you’re familiar with Redux:
FeatureXState StoreRedux
State updatesEvent handlers return new contextReducers with actions
ImmutabilityManual spreading or ImmerManual spreading
Type safetyInferred from event handlersRequires manual typing
MiddlewareEffects via enqueueMiddleware
DevToolsXState inspectorRedux DevTools
Side effectsBuilt-in with enqueue.effect()Requires middleware
// XState Store
const store = createStore({
  context: { count: 0 },
  on: {
    inc: (ctx) => ({ count: ctx.count + 1 })
  }
});
store.trigger.inc();

// Redux equivalent
const reducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'inc': return { count: state.count + 1 };
    default: return state;
  }
};
const store = createStore(reducer);
store.dispatch({ type: 'inc' });

Next Steps