@xstate/react
The @xstate/react package contains hooks and helper functions for using XState with React.
Templatesβ
Use the following templates to get started quickly with XState and React:
Installationβ
Install the latest versions of both xstate
and @xstate/react
. xstate
is a peer dependency of @xstate/react
.
- npm
- pnpm
- yarn
npm install xstate @xstate/react
pnpm install xstate @xstate/react
yarn add xstate @xstate/react
Examplesβ
Coming soon
APIβ
useActor(actorLogic, options?)
β
A React hook that creates an actor from the the given actorLogic
and starts an actor that runs for the lifetime of the component.
Argumentsβ
actorLogic
- The actor logic to create an actor from; e.g.createMachine(...)
,fromPromise(...)
, etc.options?
(optional) - Actor options.
Returns a tuple of [snapshot, send, actorRef]
:
snapshot
- Represents the current state of the actor.send
- A function that sends events to the running actor.actorRef
- The started actor.
Examplesβ
import { fromPromise } from 'xstate';
import { useActor } from '@xstate/react';
const promiseLogic = fromPromise(async () => {
const data = await getData(/* ... */);
return data;
});
function Component() {
const [state, send] = useActor(promiseLogic);
if (state.status === 'done') {
return <div>{state.output}</div>;
}
if (state.status === 'active') {
return <div>Loading...</div>;
}
return null;
}
useMachine(machine, options?)
β
A React hook that creates an actor from the given machine
and starts an actor that runs for the lifetime of the component.
Argumentsβ
machine
- An XState machineoptions?
(optional) - Actor options.
Returns a tuple of [snapshot, send, actorRef]
:
snapshot
- Represents the current state of the machine.send
- A function that sends events to the running actor.actorRef
- The started actor.
Examplesβ
import { useMachine } from '@xstate/react';
function Component() {
const [snapshot, send] = useMachine(machine);
// Machine with provided implementations
// Will keep provided implementations up-to-date
const [snapshot, send] = useMachine(
machine.provide({
actions: {
doSomething: ({ context }) => {
// ...
},
},
}),
);
}
useActorRef(machine, options?)
β
A React hook that returns the actorRef
created from the machine
with actor options
that are passed to createActor(logic, options)
, if specified. It starts the actor ref and runs it for the lifetime of the component.
The useActorRef(...)
hook 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 machine actor) which will not rerender when its state changes.
You can use the useSelector(...)
hook to select part of the snapshot from the actorRef
whenever it updates.
Argumentsβ
actorLogic
- The actor logic to create an actor from; e.g.createMachine(...)
,fromPromise(...)
, etc.options?
(optional) - Actor options.
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 refselector
- 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 theactor
.- Defaults to attempting to get the
actor.state
, or returningundefined
if that does not exist. Will automatically pull the state from actor refs.
- Defaults to attempting to get the
Examplesβ
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(logic)
β
Returns a React Context object that creates an actor from the provided actor logic
and makes the actor available through React Context. There are helper methods for accessing state and the actor ref.
Argumentsβ
logic
- Actor logic, like an XState machine
Returns a React Context object that contains the following properties:
Provider
- a React Context Provider component with the following props:logic
- Actor logic, such as an XState machine ,that must be of the same type as the actor logic passed tocreateActorContext(...)
useSelector(selector, compare?)
- a React hook that takes in aselector
function and optionalcompare
function and returns the selected value from the actor snapshotuseActorRef()
- a React hook that returns the actor ref of the actor created from the actorlogic
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.useActorRef();
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
logic={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.error,
}),
},
},
},
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;
}
};
Inputβ
You can provide input to actors
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.snapshot
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, {
snapshot: 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;
}, [actorRef]); // note: actor ref should never change
Resourcesβ
Coming soon