Skip to content
Version: XState v4

XState in React

You can use XState with React to:

  • Coordinate local state
  • Manage global state performantly
  • Consume data from other hooks

We provide the official @xstate/react package to help you manage the integration. The package provides several hooks and helpers to get you started.

Installation

Install the @xstate/react package using npm:

npm install xstate @xstate/react

useMachine hook

The simplest way to get started with interpreting actors in React is useMachine. useMachine is a React hook that interprets the given machine and starts an actor that runs for the lifetime of the component.

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

const machine = createMachine({});

const Component = () => {
const [
// The current state of the actor
state,
// A function to send the machine events
send,
// The running actor - used for passing to `useActor`
actor,
] = useMachine(machine);

return null;
};

You can also pass machine options to the second argument of useMachine. These options will be kept up to date when the component re-renders, which means they can safely access variables inside the component’s scope:

const useLoggedInUserId = (): string => '123';

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

const machine = createMachine({
entry: 'consoleLogUserId',
});

const Component = () => {
const id = useLoggedInUserId();

const [state, send] = useMachine(machine, {
actions: {
consoleLogUserId: () => {
console.log(id);
},
},
});

return null;
};

useInterpret hook

useMachine automatically subscribes to the current state of the machine, which means every state update will result in a re-render of the component that calls it. This re-rendering isn’t always desirable.

useInterpret allows you to interpret a machine without subscribing to its updates, which means that by default, it won’t cause any re-rendering in the component.

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

const machine = createMachine({});

const Component = () => {
const actor = useInterpret(machine);

return null;
};

useInterpret accepts the same arguments as useMachine, and follows the same rules with options:

const useLoggedInUserId = (): string => '123';

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

const machine = createMachine({
entry: 'consoleLogUserId',
});

const Component = () => {
const id = useLoggedInUserId();

const actor = useInterpret(machine, {
actions: {
consoleLogUserId: () => {
console.log(id);
},
},
});

return null;
};

useSelector

You can use useSelector to subscribe to a machine created with useInterpret or interpret. useSelector gives you fine-grained control over when your components should re-render and is particularly valuable for good performance.

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

const machine = createMachine({
initial: 'hovered',
states: {
hovered: {},
notHovered: {},
},
});

const selector = (state: StateFrom<typeof machine>) => state.matches('hovered');

const Component = () => {
const actor = useInterpret(machine);

const isHovered = useSelector(actor, selector);

return null;
};

In the example above, the component will only re-render when the isHovered value changes from true to false.

Internally, useSelector compares the previous value (prev) and the next value (next) to determine whether a re-render is required. Strict equality is used for its default check: prev === next. If the check returns true, there will be no re-render.

You can customize the check by passing a compare function to useSelector:

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

const machine = createMachine({
context: {
numbers: [1, 2, 3],
},
});

const getNumbers = (state: StateFrom<typeof machine>) => state.context.numbers;

const Component = () => {
const actor = useInterpret(machine);

const numbers = useSelector(actor, getNumbers, (prev, next) => {
/**
* Checks if 1,2,3 === 2,3,4
*/
return prev.join() === next.join();
});

return null;
};

The compare function is needed in the example above because comparing two arrays by [] === [] would always result in a re-render.

useActor

Use useActor if you want to subscribe to all updates to an actor from useInterpret:

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

const machine = createMachine({
initial: 'hovered',
states: {
hovered: {},
notHovered: {},
},
});

const Component = () => {
const actor = useInterpret(machine);

const [
// The current state of the actor
state,
// A function to send the machine events
send,
] = useActor(actor);

const isHovered = state.matches('hovered');

return null;
};

useActor subscribes to all state updates from the actor, providing a similar return type to useMachine.