Zustand XState Middleware

raw JSON →
3.0.1 verified Thu Apr 23 auth: no javascript

Zustand middleware for integrating XState state machines into a global Zustand store. Currently at version 3.0.1, this package is designed to work seamlessly with XState v5 and Zustand v4, ensuring compatibility with their latest APIs and features. It provides a straightforward mechanism to create a Zustand store from an XState machine, exposing the machine's current `state`, the `send` event function, and the underlying `actor` for component consumption. This enables developers to leverage Zustand's reactive state management and selector capabilities to optimize re-renders while benefiting from XState's robust state orchestration. The middleware also allows passing actor options, such as `devTools`, directly to the XState actor, enhancing debugging and development workflows. While specific release cadences aren't explicitly stated, its alignment with major versions of its peer dependencies suggests active maintenance in response to their evolution.

error TypeError: create is not a function
cause Attempting to import `create` from 'zustand' as a named export when it is a default export in Zustand v4+.
fix
Change import { create } from 'zustand' to import create from 'zustand'.
error TypeError: machine.createMachine is not a function
cause Passing an XState v4 machine definition or an incorrectly structured object to the middleware, which expects an XState v5 machine created with `setup().createMachine()`.
fix
Ensure your XState machine is created using const machine = setup({ ... }).createMachine({ ... }); before passing machine to xstate(machine).
error TypeError: Cannot read properties of undefined (reading 'value') when accessing state.value
cause This usually indicates the XState machine or the Zustand store was not correctly initialized, or a peer dependency (XState/Zustand) version mismatch is preventing the middleware from creating a valid machine instance.
fix
Verify that xstate (v5+) and zustand (v4+) are correctly installed and that the machine passed to the middleware is a valid XState v5 machine instance. Check console for earlier initialization errors.
breaking This middleware requires XState v5+ and Zustand v4+ as peer dependencies. Older versions of XState (e.g., v4) are not compatible due to significant API changes in XState v5, particularly around machine creation and actor management.
fix Ensure your project uses `xstate@^5.15.0` and `zustand@^4.1.4` (or compatible versions) by upgrading your `package.json` dependencies and running `npm install`.
gotcha When migrating from XState v4, remember that XState v5 introduces the `setup` API for machine definition. Directly passing old XState v4 machine definition objects will lead to runtime errors.
fix Refactor your XState machine definitions to use `setup({ ... }).createMachine({ ... })` as shown in the XState v5 documentation and the quickstart example.
gotcha The `create` function from `zustand` is a default export in v4+, not a named export. Incorrect import syntax will cause runtime errors.
fix Use `import create from 'zustand'` instead of `import { create } from 'zustand'` for Zustand v4 and newer.
gotcha The `xstate` middleware expects an already created XState machine instance (the return value of `setup(...).createMachine(...)`), not just a plain machine configuration object.
fix Always pass the result of `setup({ ... }).createMachine({ ... })` to the `xstate` middleware function.
npm install zustand-middleware-xstate
yarn add zustand-middleware-xstate
pnpm add zustand-middleware-xstate

Demonstrates creating an XState machine, integrating it into a Zustand store with the middleware, and accessing state and send function.

import create from "zustand";
import { setup } from "xstate";
import xstate from "zustand-middleware-xstate";

type UserEvents = { type: 'LOGIN' } | { type: 'LOGOUT' };

// Create your XState machine definition using setup
const authMachine = setup({
  types: {
    events: {} as UserEvents,
    context: {} as { userId: string | null }
  },
}).createMachine({
  id: "auth",
  initial: "loggedOut",
  context: { userId: null },
  states: {
    loggedOut: {
      on: {
        LOGIN: {
          target: "loggedIn",
          actions: ({ context }) => {
            // In a real app, you'd set the actual user ID
            context.userId = 'some-user-id-' + Math.random().toFixed(2);
          }
        }
      }
    },
    loggedIn: {
      on: {
        LOGOUT: {
          target: "loggedOut",
          actions: ({ context }) => {
            context.userId = null;
          }
        }
      }
    }
  }
});

// Create a Zustand store hook using the xstate middleware
const useAuthStore = create(
  xstate(authMachine, { devTools: process.env.NODE_ENV === 'development' })
);

// Example usage in a component-like structure
const AuthComponent = () => {
  const { state, send, actor } = useAuthStore();

  console.log('Current Auth State:', state.value);
  console.log('User ID:', state.context.userId);

  return (
    <div>
      <p>Authentication Status: {String(state.value)}</p>
      {state.matches('loggedOut') && (
        <button onClick={() => send({ type: 'LOGIN' })}>Login</button>
      )}
      {state.matches('loggedIn') && (
        <button onClick={() => send({ type: 'LOGOUT' })}>Logout</button>
      )}
      <p>Actor status: {actor.getSnapshot().status}</p>
    </div>
  );
};

// Simulate rendering
AuthComponent(); // Initial render
useAuthStore.getState().send({ type: 'LOGIN' }); // Simulate user login
AuthComponent(); // Re-render after login
useAuthStore.getState().send({ type: 'LOGOUT' }); // Simulate user logout
AuthComponent(); // Re-render after logout