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 (theusernameproperty)
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,
}),
},
},
},
},
},
});