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:
bash
npm install xstate @xstate/react
bash
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.
tsx
import {createMachine } from 'xstate';import {useMachine } from '@xstate/react';Âconstmachine =createMachine ({});ÂconstComponent = () => {const [// The current state of the actorstate ,// A function to send the machine eventssend ,// The running actor - used for passing to `useActor`actor ,] =useMachine (machine );Âreturn null;};
tsx
import {createMachine } from 'xstate';import {useMachine } from '@xstate/react';Âconstmachine =createMachine ({});ÂconstComponent = () => {const [// The current state of the actorstate ,// A function to send the machine eventssend ,// 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:
tsx
import {createMachine } from 'xstate';import {useMachine } from '@xstate/react';Âconstmachine =createMachine ({entry : 'consoleLogUserId',});ÂconstComponent = () => {constid =useLoggedInUserId ();Âconst [state ,send ] =useMachine (machine , {actions : {consoleLogUserId : () => {console .log (id );},},});Âreturn null;};
tsx
import {createMachine } from 'xstate';import {useMachine } from '@xstate/react';Âconstmachine =createMachine ({entry : 'consoleLogUserId',});ÂconstComponent = () => {constid =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.
tsx
import {createMachine } from 'xstate';import {useInterpret } from '@xstate/react';Âconstmachine =createMachine ({});ÂconstComponent = () => {constactor =useInterpret (machine );Âreturn null;};
tsx
import {createMachine } from 'xstate';import {useInterpret } from '@xstate/react';Âconstmachine =createMachine ({});ÂconstComponent = () => {constactor =useInterpret (machine );Âreturn null;};
useInterpret
accepts the same arguments as useMachine
, and follows the same rules with options
:
tsx
import {createMachine } from 'xstate';import {useInterpret } from '@xstate/react';Âconstmachine =createMachine ({entry : 'consoleLogUserId',});ÂconstComponent = () => {constid =useLoggedInUserId ();Âconstactor =useInterpret (machine , {actions : {consoleLogUserId : () => {console .log (id );},},});Âreturn null;};
tsx
import {createMachine } from 'xstate';import {useInterpret } from '@xstate/react';Âconstmachine =createMachine ({entry : 'consoleLogUserId',});ÂconstComponent = () => {constid =useLoggedInUserId ();Âconstactor =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.
tsx
import {createMachine ,StateFrom } from 'xstate';import {useInterpret ,useSelector } from '@xstate/react';Âconstmachine =createMachine ({initial : 'hovered',states : {hovered : {},notHovered : {},},});Âconstselector = (state :StateFrom <typeofmachine >) =>state .matches ('hovered');ÂconstComponent = () => {constactor =useInterpret (machine );ÂconstisHovered =useSelector (actor ,selector );Âreturn null;};
tsx
import {createMachine ,StateFrom } from 'xstate';import {useInterpret ,useSelector } from '@xstate/react';Âconstmachine =createMachine ({initial : 'hovered',states : {hovered : {},notHovered : {},},});Âconstselector = (state :StateFrom <typeofmachine >) =>state .matches ('hovered');ÂconstComponent = () => {constactor =useInterpret (machine );ÂconstisHovered =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
:
tsx
import {createMachine ,StateFrom } from 'xstate';import {useInterpret ,useSelector } from '@xstate/react';Âconstmachine =createMachine ({context : {numbers : [1, 2, 3],},});ÂconstgetNumbers = (state :StateFrom <typeofmachine >) =>state .context .numbers ;ÂconstComponent = () => {constactor =useInterpret (machine );Âconstnumbers =useSelector (actor ,getNumbers , (prev ,next ) => {/*** Checks if 1,2,3 === 2,3,4*/returnprev .join () ===next .join ();});Âreturn null;};
tsx
import {createMachine ,StateFrom } from 'xstate';import {useInterpret ,useSelector } from '@xstate/react';Âconstmachine =createMachine ({context : {numbers : [1, 2, 3],},});ÂconstgetNumbers = (state :StateFrom <typeofmachine >) =>state .context .numbers ;ÂconstComponent = () => {constactor =useInterpret (machine );Âconstnumbers =useSelector (actor ,getNumbers , (prev ,next ) => {/*** Checks if 1,2,3 === 2,3,4*/returnprev .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
:
tsx
import {createMachine ,StateFrom } from 'xstate';import {useInterpret ,useActor } from '@xstate/react';Âconstmachine =createMachine ({initial : 'hovered',states : {hovered : {},notHovered : {},},});ÂconstComponent = () => {constactor =useInterpret (machine );Âconst [// The current state of the actorstate ,// A function to send the machine eventssend ,] =useActor (actor );ÂconstisHovered =state .matches ('hovered');Âreturn null;};
tsx
import {createMachine ,StateFrom } from 'xstate';import {useInterpret ,useActor } from '@xstate/react';Âconstmachine =createMachine ({initial : 'hovered',states : {hovered : {},notHovered : {},},});ÂconstComponent = () => {constactor =useInterpret (machine );Âconst [// The current state of the actorstate ,// A function to send the machine eventssend ,] =useActor (actor );ÂconstisHovered =state .matches ('hovered');Âreturn null;};
useActor
subscribes to all state updates from the actor, providing a similar return type to useMachine
.