Using the machine inside a component works the same as fromTransition.
In fact, since a state machine is itself an actor, we can use the useActor hook the same as we did with fromTransition:
import { useActor } from "@xstate/react";
import { setup } from "xstate";
type Event = { type: "toggle" };
const machine = setup({
types: {
events: {} as Event,
},
}).createMachine({
initial: "Off",
states: {
Off: {
on: {
toggle: { target: "On" },
},
},
On: {
on: {
toggle: { target: "Off" },
},
},
},
});
export default function Machine() {
const [snapshot, send] = useActor(machine);
return (<></>);
}XState also has a
useMachinehook, which is an alias foruseActorspecific to state machines.
The key difference is that we don't get a boolean value anymore, but instead the current state of the machine ("On" or "Off").
We extract the current state using snapshot.value:
The type of
snapshot.valueis inferred based onstatesdefined in the machine. In this example it's"On" | "Off"as expected.
import { useActor } from "@xstate/react";
import { setup } from "xstate";
type Event = { type: "toggle" };
const machine = setup({
types: {
events: {} as Event,
},
}).createMachine({
initial: "Off",
states: {
Off: {
on: {
toggle: { target: "On" },
},
},
On: {
on: {
toggle: { target: "Off" },
},
},
},
});
export default function Machine() {
const [snapshot, send] = useActor(machine);
return (
<input
type="checkbox"
checked={snapshot.value === "On"}
onChange={() => send({ type: "toggle" })}
/>
);
}Let's recap what we did in order:
- Define the
statesof the machine - Define the initial state using
initial - Define
Eventwith all possible events - Define transitions for each state using
on+target - Use the machine inside a component using
useActor
import { useActor } from "@xstate/react";
import { setup } from "xstate";
type Event = { type: "toggle" };
const machine = setup({
types: {
// 3️⃣ Define events
events: {} as Event,
},
}).createMachine({
// 2️⃣ Define initial state
initial: "Off",
// 1️⃣ Define states
states: {
Off: {
// 4️⃣ Define transitions
on: {
toggle: { target: "On" },
},
},
On: {
// 4️⃣ Define transitions
on: {
toggle: { target: "Off" },
},
},
},
});
export default function Machine() {
// 5️⃣ Use the machine inside a component with `useActor`
const [snapshot, send] = useActor(machine);
return (
<input
type="checkbox"
checked={snapshot.value === "On"}
onChange={() => send({ type: "toggle" })}
/>
);
}Whereas the UI code looks similar to the other examples, the logic is completely different from before.
That's because state machines are a different model of state management. Instead of working directly with a store (object), state machines work with states and transitions.
This is why XState is generally considered more complex than useReducer or useState.
We are going to appreciate the advantages of this state-based approach as we explore more complex use cases in the course.
