Stately

@xstate/store-react

The @xstate/store-react package provides React bindings for XState Store. It includes hooks for subscribing to store state, creating component-scoped stores, and using atoms.

These are the docs for @xstate/store-react v2. For v1 docs, see v1 docs.

This package re-exports all of @xstate/store, so you only need to install @xstate/store-react.

Installation

npm install @xstate/store-react
pnpm install @xstate/store-react
yarn add @xstate/store-react

Quick start

import { createStore, useSelector } from '@xstate/store-react'; 

const store = createStore({
  context: { count: 0 },
  on: {
    inc: (context, event: { by?: number }) => ({
      count: context.count + (event.by ?? 1),
    }),
  },
});

function Counter() {
  const count = useSelector(store, (state) => state.context.count);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => store.trigger.inc()}>+1</button>
      <button onClick={() => store.trigger.inc({ by: 5 })}>+5</button>
    </div>
  );
}

API

useSelector(store, selector?, compare?)

Subscribes to a store or atom and returns a selected value. The component re-renders when the selected value changes.

import { useSelector } from '@xstate/store-react'; 

function Counter() {
  const count = useSelector(store, (state) => state.context.count);

  const user = useSelector(
    store,
    (state) => state.context.user,
    (prev, next) => prev.id === next.id
  );

  return <div>Count: {count}</div>;
}

Parameters:

  • store - The store or atom to subscribe to
  • selector - Optional function to select a value
  • compare - Optional comparison function (defaults to strict equality ===)

Returns: The selected value

useStore(configOrLogic, input?)

Creates a component-scoped store instance. Pass either a store config or store logic created with createStoreLogic(...).

import { createStoreLogic, useSelector, useStore } from '@xstate/store-react'; 

const counterLogic = createStoreLogic({
  context: (input: { initialCount: number }) => ({
    count: input.initialCount,
  }),
  on: {
    inc: (context) => ({ count: context.count + 1 }),
    dec: (context) => ({ count: context.count - 1 }),
  },
});

function Counter() {
  const store = useStore(counterLogic, { initialCount: 0 });
  const count = useSelector(store, (state) => state.context.count);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => store.trigger.inc()}>+</button>
      <button onClick={() => store.trigger.dec()}>-</button>
    </div>
  );
}

If the store logic requires input, the input argument is required. If the input is optional, useStore(logic) is allowed.

Parameters:

  • configOrLogic - Store configuration object or store logic
  • input - Optional or required input for store logic

Returns: A store instance, stable across re-renders

useAtom(atomOrConfig, selectorOrInput?, compare?)

Subscribes to an atom and returns its value. You can pass an existing atom, or an atom config created with createAtomConfig(...).

import { createAtom, useAtom } from '@xstate/store-react'; 

const countAtom = createAtom(0);

function Counter() {
  const count = useAtom(countAtom);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => countAtom.set((prev) => prev + 1)}>+</button>
    </div>
  );
}

With an atom config:

import { createAtomConfig, useAtom } from '@xstate/store-react'; 

const countConfig = createAtomConfig((input: { initialCount: number }) => {
  return input.initialCount;
});

function Counter() {
  const count = useAtom(countConfig, { initialCount: 0 });

  return <div>Count: {count}</div>;
}

Parameters:

  • atomOrConfig - Existing atom or atom config
  • selectorOrInput - Selector for existing atoms, or input for atom configs
  • compare - Optional comparison function for existing atoms

Returns: The selected atom value

useAtomState(atomOrConfig, input?)

Creates or subscribes to an atom for the lifetime of a React component. It returns the current value and the live atom instance.

import { createAtomConfig, useAtomState } from '@xstate/store-react'; 

const countConfig = createAtomConfig((input: { initialCount: number }) => {
  return input.initialCount;
});

function Counter() {
  const [count, countAtom] = useAtomState(countConfig, { initialCount: 0 });

  return (
    <button onClick={() => countAtom.set((count) => count + 1)}>
      {count}
    </button>
  );
}

Parameters:

  • atomOrConfig - Existing atom or atom config
  • input - Optional or required input for atom configs

Returns: [value, atom]

createStoreHook(config)

Creates a custom React hook that combines useSelector() with a store created from a config object.

import { createStoreHook } from '@xstate/store-react'; 

const useCountStore = createStoreHook({
  context: { count: 0 },
  on: {
    inc: (ctx, event: { by: number }) => ({
      count: ctx.count + event.by,
    }),
  },
});

function Counter() {
  const [count, store] = useCountStore((s) => s.context.count);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => store.trigger.inc({ by: 1 })}>+1</button>
    </div>
  );
}

Parameters:

  • config - Store configuration object

Returns: A custom hook that returns [selectedValue, store]

Full documentation

For complete XState Store documentation including context, transitions, effects, atoms, and more, see the XState Store docs.

On this page