@xstate/react
provides a hook called useActor
that allows us to connect an actor to a component.
The useActor
hook accepts an actor and returns an array with two values:
snapshot
: contains the current context and status of the actorsend
: function that allows us to send events to the actor
import { useActor } from "@xstate/react";
import { fromTransition } from "xstate";
type Event = { type: "toggle" };
type Context = boolean;
const initialContext = false;
const actor = fromTransition((context: Context, event: Event) => {
if (event.type === "toggle") {
return !context;
}
return context;
}, initialContext);
export default function Actor() {
const [snapshot, send] = useActor(actor);
return (<></>);
}
snapshot
andsend
are two common naming conventions used for the result of react hooks in XState.We don't use the name
context
becausesnapshot
contains more than just thecontext
value.
send
instead works the same asdispatch
inuseReducer
.
The UI code is nearly the exact same as useReducer
:
import { useActor } from "@xstate/react";
import { fromTransition } from "xstate";
type Event = { type: "toggle" };
type Context = boolean;
const initialContext = false;
const actor = fromTransition((context: Context, event: Event) => {
if (event.type === "toggle") {
return !context;
}
return context;
}, initialContext);
export default function Actor() {
const [snapshot, send] = useActor(actor);
return (
<input
type="checkbox"
checked={snapshot.context}
onChange={() => send({ type: "toggle" })}
/>
);
}
That's all!
As you can see, the code is almost identical to the useReducer
example.
We can even share the same reducer
function. It's clear how the only difference is where initialContext
is provided:
import { useActor } from "@xstate/react";
import { fromTransition } from "xstate";
type Event = { type: "toggle" };
type Context = boolean;
const initialContext = false;
const reducer = (context: Context, event: Event): Context => {
if (event.type === "toggle") {
return !context;
}
return context;
};
// 👆 All shared code
// 👇 With `useReducer`
function UseReducer() {
const [context, dispatch] = useReducer(reducer, initialContext);
return (
<input
type="checkbox"
checked={context}
onChange={() => dispatch({ type: "toggle" })}
/>
);
}
// 👇 With `useActor`
const actor = fromTransition(reducer, initialContext);
function Actor() {
const [snapshot, send] = useActor(actor);
return (
<input
type="checkbox"
checked={snapshot.context}
onChange={() => send({ type: "toggle" })}
/>
);
}
XState is not necessarily more complex than normal react hooks.
You can create multiple types of actors, sometimes as simple as
fromTransition
for use cases similar touseReducer
.Again, state machines are more complex and complete, but they are not necessary for every use case.
The key building blocks that you will find in all actors are:
- Event-driven programming: actors communicate by sending and receiving events
- Internal state: actors have their own state, not accessible from the outside