@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 (<></>);
}
snapshotandsendare two common naming conventions used for the result of react hooks in XState.We don't use the name
contextbecausesnapshotcontains more than just thecontextvalue.
sendinstead works the same asdispatchinuseReducer.
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
fromTransitionfor 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
