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
useMachine
hook, which is an alias foruseActor
specific 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.value
is inferred based onstates
defined 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
states
of the machine - Define the initial state using
initial
- Define
Event
with 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.