With XState we always start by setting up the state machine using types
, states
and initial
. We define the initial state as "Editing":
import { setup } from "xstate";
import { initialContext, type Context } from "./shared";
const machine = setup({
types: {
context: {} as Context,
},
}).createMachine({
context: initialContext,
initial: "Editing",
states: {
Editing: {},
},
});
We also add an event to update the username
and capture it inside the "Editing" state:
import { setup } from "xstate";
import { initialContext, type Context } from "./shared";
type Event = { type: "update-username"; username: string };
const machine = setup({
types: {
context: {} as Context,
events: {} as Event,
},
}).createMachine({
context: initialContext,
initial: "Editing",
states: {
Editing: {
on: {
"update-username": // 👈 Capture the event inside "Editing"
}
},
},
});
Since we want to update the context, we need to use assign
inside actions
.
assign
provides access to the event payload (the username
property inside update-username
) that we use to update the context:
import { setup } from "xstate";
import { initialContext, type Context } from "./shared";
type Event = { type: "update-username"; username: string };
const machine = setup({
types: {
context: {} as Context,
events: {} as Event,
},
}).createMachine({
context: initialContext,
initial: "Editing",
states: {
Editing: {
on: {
"update-username": {
actions: assign(({ event }) => ({
// 👇 Access to the `event` payload and assign it to context
username: event.username,
})),
},
}
},
},
});
With this we can already implement the initial component code:
export default function Machine() {
const [snapshot, send] = useActor(machine);
return (
<form>
<input
type="text"
value={snapshot.context.username}
onChange={(e) =>
send({ type: "update-username", username: e.target.value })
}
/>
<button>Confirm</button>
</form>
);
}
Action with input inside setup
As we saw before, actions can be defined inside setup
as well.
We first add a new onUpdateUsername
action to setup/actions
, which uses assign
since we want to update context
:
import { assign, setup } from "xstate";
import { initialContext, type Context } from "./shared";
type Event = { type: "update-username"; username: string };
const machine = setup({
types: {
context: {} as Context,
events: {} as Event,
},
actions: {
onUpdateUsername: assign(() => /* TODO */),
},
}).createMachine({
context: initialContext,
initial: "Editing",
states: {
Editing: {},
},
});
Passing an input to an action is achieved by typing the second argument of assign
as an object with a username
property:
import { assign, setup } from "xstate";
import { initialContext, type Context } from "./shared";
type Event = { type: "update-username"; username: string };
const machine = setup({
types: {
context: {} as Context,
events: {} as Event,
},
actions: {
onUpdateUsername: assign((_, { username }: { username: string }) => ({
username,
})),
},
}).createMachine({
context: initialContext,
initial: "Editing",
states: {
Editing: {},
},
});
The first parameter contains the current context and other metadata. This second argument represents the required input for the action.
When we now intercept the update-username
event inside the "Editing" state we will notice a type error:
import { assign, setup } from "xstate";
import { initialContext, type Context } from "./shared";
type Event = { type: "update-username"; username: string };
const machine = setup({
types: {
context: {} as Context,
events: {} as Event,
},
actions: {
onUpdateUsername: assign((_, { username }: { username: string }) => ({
username,
})),
},
}).createMachine({
context: initialContext,
initial: "Editing",
states: {
Editing: {
on: {
"update-username": { // 👈 Type error
actions: "onUpdateUsername"
},
},
},
},
});
This type error is telling us that we are required to pass the username
as an input when executing the action.
We achieve this by converting actions
to an object:
type
: name of the actionparams
: pass the input to the action by extracting the event payload (theusername
property)
import { assign, setup } from "xstate";
import { initialContext, type Context } from "./shared";
type Event = { type: "update-username"; username: string };
const machine = setup({
types: {
context: {} as Context,
events: {} as Event,
},
actions: {
// 👇 Name of the action
onUpdateUsername: assign((_, { username }: { username: string }) => ({
username,
})),
},
}).createMachine({
context: initialContext,
initial: "Editing",
states: {
Editing: {
on: {
"update-username": {
actions: {
type: "onUpdateUsername",
// 👇 `param` must return the required input for the action
params: ({ event }) => ({
username: event.username,
}),
},
},
},
},
},
});