# @xstate/react
The @xstate/react package (opens new window) contains utilities for using XState (opens new window) with React (opens new window).
# Quick Start
- Install
xstate
and@xstate/react
:
npm i xstate @xstate/react
Via CDN
<script src="https://unpkg.com/@xstate/react/dist/xstate-react.umd.min.js"></script>
By using the global variable XStateReact
or
<script src="https://unpkg.com/@xstate/react/dist/xstate-react-fsm.umd.min.js"></script>
By using the global variable XStateReactFSM
- 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('TOGGLE')}>
{state.value === 'inactive'
? 'Click to activate'
: 'Active! Click to deactivate'}
</button>
);
};
# Examples
# API
# useMachine(machine, options?)
A React hook (opens new window) that interprets the given machine
and starts a service that runs for the lifetime of the component.
Arguments
machine
- An XState machine (opens new window) or a function that lazily returns a machine:// existing machine const [state, send] = useMachine(machine); // lazily-created machine const [state, send] = useMachine(() => createMachine({ /* ... */ }) );
options
(optional) - Interpreter options (opens new window) and/or any of the following machine config options:guards
,actions
,services
,delays
,immediate
,context
,state
.
Returns a tuple of [state, send, service]
:
state
- Represents the current state of the machine as an XStateState
object.send
- A function that sends events to the running service.service
- The created service.
# useService(service)
Deprecated
In the next major version, useService(service)
will be replaced with useActor(service)
. Prefer using the useActor(service)
hook for services instead, since services are also actors.
Also, keep in mind that only a single argument (the event object) can be sent to send(eventObject)
from useActor(...)
. When migrating to useActor(...)
, refactor send(...)
calls to use only a single event object:
const [state, send] = useActor(service);
-send('CLICK', { x: 0, y: 3 });
+send({ type: 'CLICK', x: 0, y: 3 });
A React hook (opens new window) that subscribes to state changes from an existing service (opens new window).
Arguments
service
- An XState service (opens new window).
Returns a tuple of [state, send]
:
state
- Represents the current state of the service as an XStateState
object.send
- A function that sends events to the running service.
# useActor(actor, getSnapshot?)
A React hook (opens new window) that subscribes to emitted changes from an existing actor (opens new window).
Arguments
actor
- an actor-like object that contains.send(...)
and.subscribe(...)
methods.getSnapshot
- 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.
- Defaults to attempting to get the
const [state, send] = useActor(someSpawnedActor);
// with custom actors
const [state, send] = useActor(customActor, (actor) => {
// implementation-specific pseudocode example:
return actor.getLastEmittedValue();
});
# useInterpret(machine, options?, observer?)
A React hook that returns the service
created from the machine
with the options
, if specified. It starts the service and runs it for the lifetime of the component. This is similar to useMachine
; however, useInterpret
allows for a custom observer
to subscribe to the service
.
The useInterpret
is useful when you want fine-grained control, e.g. to add logging, or minimize re-renders. In contrast to useMachine
that would flush each update from the machine to the React component, useInterpret
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 service inside a render, use the useSelector(...)
hook to subscribe to it.
Since 1.3.0
Arguments
machine
- An XState machine (opens new window) or a function that lazily returns a machine.options
(optional) - Interpreter options (opens new window) and/or any of the following machine config options:guards
,actions
,services
,delays
,immediate
,context
,state
.observer
(optional) - an observer or listener that listens to state updates:- an observer (e.g.,
{ next: (state) => {/* ... */} }
) - or a listener (e.g.,
(state) => {/* ... */}
)
- an observer (e.g.,
import { useInterpret } from '@xstate/react';
import { someMachine } from '../path/to/someMachine';
const App = () => {
const service = useInterpret(someMachine);
// ...
};
With options + listener:
// ...
const App = () => {
const service = useInterpret(
someMachine,
{
actions: {
/* ... */
}
},
(state) => {
// subscribes to state changes
console.log(state);
}
);
// ...
};
# useSelector(actor, selector, compare?, getSnapshot?)
A React hook that returns the selected value from the snapshot of an actor
, such as a service. This hook will only cause a rerender if the selected value changes, as determined by the optional compare
function.
Since 1.3.0
Arguments
actor
- a service or an actor-like object that contains.send(...)
and.subscribe(...)
methods.selector
- a function that takes in an actor's "current state" (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 services.
- Defaults to attempting to get the
import { useSelector } from '@xstate/react';
// tip: optimize selectors by defining them externally when possible
const selectCount = (state) => state.context.count;
const App = ({ service }) => {
const count = useSelector(service, selectCount);
// ...
};
With compare
function:
// ...
const selectUser = (state) => state.context.user;
const compareUser = (prevUser, nextUser) => prevUser.id === nextUser.id;
const App = ({ service }) => {
const user = useSelector(service, selectUser, compareUser);
// ...
};
With useInterpret(...)
:
import { useInterpret, useSelector } from '@xstate/react';
import { someMachine } from '../path/to/someMachine';
const selectCount = (state) => state.context.count;
const App = ({ service }) => {
const service = useInterpret(someMachine);
const count = useSelector(service, selectCount);
// ...
};
# asEffect(action)
Ensures that the action
is executed as an effect in useEffect
, rather than being immediately executed.
Arguments
action
- An action function (e.g.,(context, event) => { alert(context.message) })
)
Returns a special action function that wraps the original so that useMachine
knows to execute it in useEffect
.
Example
const machine = createMachine({
initial: 'focused',
states: {
focused: {
entry: 'focus'
}
}
});
const Input = () => {
const inputRef = useRef(null);
const [state, send] = useMachine(machine, {
actions: {
focus: asEffect((context, event) => {
inputRef.current && inputRef.current.focus();
})
}
});
return <input ref={inputRef} />;
};
# asLayoutEffect(action)
Ensures that the action
is executed as an effect in useLayoutEffect
, rather than being immediately executed.
Arguments
action
- An action function (e.g.,(context, event) => { alert(context.message) })
)
Returns a special action function that wraps the original so that useMachine
knows to execute it in useLayoutEffect
.
# useMachine(machine)
with @xstate/fsm
A React hook (opens new window) that interprets the given finite state machine
from [@xstate/fsm
] and starts a service that runs for the lifetime of the component.
This special useMachine
hook is imported from @xstate/react/fsm
Arguments
machine
- An XState finite state machine (FSM) (opens new window).options
- An optionaloptions
object.
Returns a tuple of [state, send, service]
:
state
- Represents the current state of the machine as an@xstate/fsm
StateMachine.State
object.send
- A function that sends events to the running service.service
- The created@xstate/fsm
service.
Example
import { useEffect } from 'react';
import { useMachine } from '@xstate/react/fsm';
import { createMachine } from '@xstate/fsm';
const context = {
data: undefined
};
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context,
states: {
idle: {
on: { FETCH: 'loading' }
},
loading: {
entry: ['load'],
on: {
RESOLVE: {
target: 'success',
actions: assign({
data: (context, event) => event.data
})
}
}
},
success: {}
}
});
const Fetcher = ({
onFetch = () => new Promise((res) => res('some data'))
}) => {
const [state, send] = useMachine(fetchMachine, {
actions: {
load: () => {
onFetch().then((res) => {
send({ type: 'RESOLVE', data: res });
});
}
}
});
switch (state.value) {
case 'idle':
return <button onClick={(_) => send('FETCH')}>Fetch</button>;
case 'loading':
return <div>Loading...</div>;
case 'success':
return (
<div>
Success! Data: <div data-testid="data">{state.context.data}</div>
</div>
);
default:
return null;
}
};
# Configuring Machines
Existing machines can be configured by passing the machine options as the 2nd argument of useMachine(machine, options)
.
Example: the 'fetchData'
service 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.data
})
},
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, {
actions: {
notifySuccess: (ctx) => onResolve(ctx.data)
},
services: {
fetchData: (_, e) =>
fetch(`some/api/${e.query}`).then((res) => res.json())
}
});
switch (state.value) {
case 'idle':
return (
<button onClick={() => send('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('RETRY')}>Retry</button>
</>
);
default:
return null;
}
};
# Matching States
When using hierarchical (opens new window) and parallel (opens new window) machines, the state values will be objects, not strings. In this case, it is best to use state.matches(...)
(opens new window).
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(...)
(opens new window) 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
:
// ...
// Get the persisted state config object from somewhere, e.g. localStorage
const persistedState = JSON.parse(localStorage.getItem('some-persisted-state-key')) || someMachine.initialState;
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 (/* ... */)
}
# Services
The service
created in useMachine(machine)
can be referenced as the third returned value:
// vvvvvvv
const [state, send, service] = useMachine(someMachine);
You can subscribe to that service's state changes with the useEffect
hook (opens new window):
// ...
useEffect(() => {
const subscription = service.subscribe((state) => {
// simple state logging
console.log(state);
});
return subscription.unsubscribe;
}, [service]); // note: service should never change
# Migration from 0.x
For spawned actors created using
invoke
orspawn(...)
, use theuseActor()
hook instead ofuseService()
:-import { useService } from '@xstate/react'; +import { useActor } from '@xstate/react'; -const [state, send] = useService(someActor); +const [state, send] = useActor(someActor);