Skip to content
Version: XState v5

@xstate/react

The @xstate/react package contains hooks for using XState with React.

Quick start​

  1. Install xstate and @xstate/react:
yarn add xstate@beta @xstate/react@beta
  1. Import the useMachine hook:
import { useMachine } from '@xstate/react';
import { createMachine } from 'xstate';

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

export const Toggler = () => {
const [state, send] = useMachine(toggleMachine);

return (
<button onClick={() => send({ type: 'TOGGLE' })}>
{state.value === 'inactive'
? 'Click to activate'
: 'Active! Click to deactivate'}
</button>
);
};

Examples​

API​

useMachine(machine, options?)​

A React hook that interprets the given machine and starts an actor that runs for the lifetime of the component.

Arguments​

Returns a tuple of [state, send, actor]:

  • state - Represents the current state of the machine as an XState State object.
  • send - A function that sends events to the running actor.
  • actor - The started actor.
// Existing machine
const [state, send] = useMachine(machine);

// Machine with provided implementations
// Will keep provided implementations up-to-date
const [state, send] = useMachine(
machine.provide({
actions: {
doSomething: ({ context }) => {
// ...
},
},
}),
);

useActor(actorLogic)​

A React hook that interprets the given actorLogic and starts an actor that runs for the lifetime of the component.

Arguments​

  • actorLogic - The actor logic to interpret; e.g. createMachine(...), fromPromise(...), etc.
  • options? (optional) - Interpreter options
const promiseLogic = fromPromise(async () => {
const data = await getData(...);

return data;
});

const [state, send] = useActor(promiseLogic);

useActorRef(machine, options?, observer?)​

A React hook that returns the actorRef created from the machine with the options, if specified. It starts the actor ref and runs it for the lifetime of the component. This is similar to useActor; however, useActorRef allows for a custom observer to subscribe to the actorRef.

The useActorRef is useful when you want fine-grained control, e.g. to add logging, or minimize re-renders. In contrast to useActor that would flush each update from the machine to the React component, useActorRef instead returns a static reference (to just the interpreted machine) which will not rerender when its state changes.

To use a piece of state from the actorRef inside a render, use the useSelector(...) hook to subscribe to it.

Arguments​

  • actorLogic
  • options? (optional) - Interpreter options
  • observer? (optional) - an observer or listener that listens to state updates:
    • an observer (e.g., { next: (snapshot) => {/* ... */} })
    • or a listener (e.g., (snapshot) => {/* ... */})
import { useActorRef } from '@xstate/react';
import { someMachine } from '../path/to/someMachine';

const App = () => {
const actorRef = useActorRef(someMachine);

// ...
};

Providing machine implementations:

// ...

const App = () => {
const actorRef = useActorRef(
someMachine.provide({
actions: {
// ...
},
}),
);

// ...
};

useSelector(actorRef, selector, compare?, getSnapshot?)​

A React hook that returns the selected value from the snapshot of an actorRef, such as a actor ref. This hook will only cause a rerender if the selected value changes, as determined by the optional compare function.

Arguments​

  • actorRef - an actor ref
  • selector - a function that takes in an actor’s snapshot as an argument and returns the desired selected value.
  • compare (optional) - a function that determines if the current selected value is the same as the previous selected value.
  • getSnapshot (optional) - a function that should return the latest emitted value from the actor.
    • Defaults to attempting to get the actor.state, or returning undefined if that does not exist. Will automatically pull the state from actor refs.
import { useSelector } from '@xstate/react';

// tip: optimize selectors by defining them externally when possible
const selectCount = (snapshot) => snapshot.context.count;

const App = ({ actorRef }) => {
const count = useSelector(actorRef, selectCount);

// ...
};

With compare function:

// ...

const selectUser = (snapshot) => snapshot.context.user;
const compareUser = (prevUser, nextUser) => prevUser.id === nextUser.id;

const App = ({ actorRef }) => {
const user = useSelector(actorRef, selectUser, compareUser);

// ...
};

createActorContext(machine)​

Returns a React Context object that interprets the machine and makes the actor available through React Context. There are helper methods for accessing state and the actor ref.

Arguments​

Returns a React Context object that contains the following properties:

  • Provider - a React Context Provider component with the following props:
    • machine - An XState machine that must be of the same type as the machine passed to createActorContext(...)
  • useSelector(selector, compare?) - a React hook that takes in a selector function and optional compare function and returns the selected value from the actor snapshot
  • useActorRef() - a React hook that returns the actor ref of the interpreted machine

Creating a React Context for the actor and providing it in app scope:

import { createActorContext } from '@xstate/react';
import { someMachine } from '../path/to/someMachine';

const SomeMachineContext = createActorContext(someMachine);

function App() {
return (
<SomeMachineContext.Provider>
<SomeComponent />
</SomeMachineContext.Provider>
);
}

Consuming the actor in a component:

import { SomeMachineContext } from '../path/to/SomeMachineContext';

function SomeComponent() {
const count = SomeMachineContext.useSelector((state) => state.context.count);
const someactorRef = SomeMachineContext.useActor();

return (
<div>
<p>Count: {count}</p>
<button onClick={() => someactorRef.send({ type: 'inc' })}>
Increment
</button>
</div>
);
}

Providing a similar machine:

import { SomeMachineContext } from '../path/to/SomeMachineContext';
import { someMachine } from '../path/to/someMachine';

function SomeComponent() {
return (
<SomeMachineContext.Provider
machine={someMachine.provide({
actions: {
someAction: differentImplementation,
},
// ... More implementations
})}
>
<SomeOtherComponent />
</SomeMachineContext.Provider>
);
}

Shallow comparison​

The default comparison is a strict reference comparison (===). If your selector returns non-primitive values, such as objects or arrays, you should keep this in mind and either return the same reference, or provide a shallow or deep comparator.

The shallowEqual(...) comparator function is available for shallow comparison:

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

// ...

const selectUser = (state) => state.context.user;

const App = ({ actorRef }) => {
// shallowEqual comparator is needed to compare the object, whose
// reference might change despite the shallow object values being equal
const user = useSelector(actorRef, selectUser, shallowEqual);

// ...
};

With useActorRef(...):

import { useActorRef, useSelector } from '@xstate/react';
import { someMachine } from '../path/to/someMachine';

const selectCount = (state) => state.context.count;

const App = () => {
const actorRef = useActorRef(someMachine);
const count = useSelector(actorRef, selectCount);

// ...
};

Configuring machines​

Existing machines can be customized by providing different implementations in machine.provide(implementations).

Example: the 'fetchData' actor ref and 'notifySuccess' action are both configurable:

const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: {
data: undefined,
error: undefined,
},
states: {
idle: {
on: { FETCH: 'loading' },
},
loading: {
invoke: {
src: 'fetchData',
onDone: {
target: 'success',
actions: assign({
data: ({ event }) => event.output,
}),
},
onError: {
target: 'failure',
actions: assign({
error: ({ event }) => event.data,
}),
},
},
},
success: {
entry: 'notifySuccess',
type: 'final',
},
failure: {
on: {
RETRY: 'loading',
},
},
},
});

const Fetcher = ({ onResolve }) => {
const [state, send] = useMachine(
fetchMachine.provide({
actions: {
notifySuccess: ({ context }) => onResolve(context.data),
},
actors: {
fetchData: fromPromise(() =>
fetch(`some/api/${e.query}`).then((res) => res.json()),
),
},
}),
);

switch (state.value) {
case 'idle':
return (
<button onClick={() => send({ type: 'FETCH', query: 'something' })}>
Search for something
</button>
);
case 'loading':
return <div>Searching...</div>;
case 'success':
return <div>Success! Data: {state.context.data}</div>;
case 'failure':
return (
<>
<p>{state.context.error.message}</p>
<button onClick={() => send({ type: 'RETRY' })}>Retry</button>
</>
);
default:
return null;
}
};

Matching states​

When using hierarchical and parallel machines, the state values will be objects, not strings. In this case, it is best to use state.matches(...).

We can do this with if/else if/else blocks:

// ...
if (state.matches('idle')) {
return /* ... */;
} else if (state.matches({ loading: 'user' })) {
return /* ... */;
} else if (state.matches({ loading: 'friends' })) {
return /* ... */;
} else {
return null;
}

We can also continue to use switch, but we must make an adjustment to our approach. By setting the expression of the switch to true, we can use state.matches(...) as a predicate in each case:

switch (true) {
case state.matches('idle'):
return /* ... */;
case state.matches({ loading: 'user' }):
return /* ... */;
case state.matches({ loading: 'friends' }):
return /* ... */;
default:
return null;
}

A ternary statement can also be considered, especially within rendered JSX:

const Loader = () => {
const [state, send] = useMachine(/* ... */);

return (
<div>
{state.matches('idle') ? (
<Loader.Idle />
) : state.matches({ loading: 'user' }) ? (
<Loader.LoadingUser />
) : state.matches({ loading: 'friends' }) ? (
<Loader.LoadingFriends />
) : null}
</div>
);
};

Persisted and rehydrated State​

You can persist and rehydrate state with useMachine(...) via options.state in the 2nd argument:

// ...

// Get the persisted state config object from somewhere, e.g. localStorage
const persistedState = JSON.parse(localStorage.getItem('some-persisted-state-key'));

const App = () => {
const [state, send] = useMachine(someMachine, {
state: persistedState // provide persisted state config object here
});

// state will initially be that persisted state, not the machine’s initialState

return (/* ... */)
}

Actor refs​

The actorRef created in useMachine(machine) can be referenced as the third returned value:

//                  vvvvvvvv
const [state, send, actorRef] = useMachine(someMachine);

You can subscribe to that actor ref's state changes with the useEffect hook:

// ...

useEffect(() => {
const subscription = actorRef.subscribe((snapshot) => {
// simple logging
console.log(snapshot);
});

return subscription.unsubscribe;
}, [service]); // note: actor ref should never change

Resources​

Coming soon