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.