Skip to main content
The @xstate/react package provides React hooks and utilities for using XState actors in React applications.

Installation

npm install @xstate/react xstate

Requirements

  • React 16.8.0 or higher (requires hooks support)
  • XState 5.0.0 or higher

Available Hooks

The package exports the following hooks:

Core Hooks

  • useActor - Create and subscribe to an actor from logic
  • useActorRef - Create an actor ref without subscribing to state changes
  • useSelector - Subscribe to derived state from an actor

Context Utilities

Quick Start

Basic Usage with useActor

import { useActor } from '@xstate/react';
import { createMachine } from 'xstate';

const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: {
      on: { TOGGLE: 'active' }
    },
    active: {
      on: { TOGGLE: 'inactive' }
    }
  }
});

function Toggle() {
  const [state, send] = useActor(toggleMachine);

  return (
    <button onClick={() => send({ type: 'TOGGLE' })}
      {state.value === 'active' ? 'ON' : 'OFF'}
    </button>
  );
}

Using Context for Shared State

import { createActorContext } from '@xstate/react';
import { createMachine } from 'xstate';

const userMachine = createMachine({
  id: 'user',
  initial: 'loggedOut',
  context: { user: null },
  states: {
    loggedOut: {
      on: {
        LOGIN: {
          target: 'loggedIn',
          actions: assign({
            user: ({ event }) => event.user
          })
        }
      }
    },
    loggedIn: {
      on: {
        LOGOUT: {
          target: 'loggedOut',
          actions: assign({ user: null })
        }
      }
    }
  }
});

const UserContext = createActorContext(userMachine);

function App() {
  return (
    <UserContext.Provider>
      <UserProfile />
      <LoginButton />
    </UserContext.Provider>
  );
}

function UserProfile() {
  const user = UserContext.useSelector((state) => state.context.user);
  
  if (!user) return null;
  
  return <div>Welcome, {user.name}!</div>;
}

function LoginButton() {
  const actorRef = UserContext.useActorRef();
  const isLoggedIn = UserContext.useSelector((state) => 
    state.matches('loggedIn')
  );

  return (
    <button
      onClick={() => {
        if (isLoggedIn) {
          actorRef.send({ type: 'LOGOUT' });
        } else {
          actorRef.send({ 
            type: 'LOGIN', 
            user: { name: 'Alice' } 
          });
        }
      }}
    >
      {isLoggedIn ? 'Logout' : 'Login'}
    </button>
  );
}

Optimizing Re-renders with useSelector

import { useActorRef, useSelector } from '@xstate/react';
import { createMachine } from 'xstate';

const counterMachine = createMachine({
  context: { count: 0, lastUpdate: null },
  on: {
    INCREMENT: {
      actions: assign({
        count: ({ context }) => context.count + 1,
        lastUpdate: () => new Date().toISOString()
      })
    }
  }
});

function Counter() {
  const actorRef = useActorRef(counterMachine);
  
  return (
    <div>
      <CountDisplay actorRef={actorRef} />
      <LastUpdate actorRef={actorRef} />
      <button onClick={() => actorRef.send({ type: 'INCREMENT' })}
        Increment
      </button>
    </div>
  );
}

// Only re-renders when count changes
function CountDisplay({ actorRef }) {
  const count = useSelector(actorRef, (state) => state.context.count);
  
  return <div>Count: {count}</div>;
}

// Only re-renders when lastUpdate changes
function LastUpdate({ actorRef }) {
  const lastUpdate = useSelector(
    actorRef, 
    (state) => state.context.lastUpdate
  );
  
  return <div>Last update: {lastUpdate}</div>;
}

Server-Side Rendering

All hooks are compatible with server-side rendering (SSR). The package uses use-sync-external-store shim to ensure compatibility with React 18’s concurrent features and SSR.
import { useActor } from '@xstate/react';
import { createMachine } from 'xstate';

const dataMachine = createMachine({
  initial: 'loading',
  states: {
    loading: {
      invoke: {
        src: 'fetchData',
        onDone: {
          target: 'success',
          actions: assign({
            data: ({ event }) => event.output
          })
        },
        onError: 'failure'
      }
    },
    success: {},
    failure: {}
  }
});

function DataComponent({ initialData }) {
  const [state] = useActor(dataMachine, {
    input: { data: initialData }
  });

  if (state.matches('loading')) {
    return <div>Loading...</div>;
  }

  if (state.matches('failure')) {
    return <div>Error loading data</div>;
  }

  return <div>{JSON.stringify(state.context.data)}</div>;
}

Migration from v4

If you’re upgrading from @xstate/react v4:
  • useMachine has been deprecated in favor of useActor
  • useInterpret has been deprecated in favor of useActorRef
  • Actor refs are now automatically started and stopped
  • The third return value from useActor is now the actor ref instead of the service

Next Steps