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
.