Synchronous effects like updating the context are called actions in XState.
actions in XState are synchronous functions. actions are responsible to execute side effects, like for example changing context.
actionsare executed as part of a state transition.We saw before that when no next state is defined (no
target) the machine performs a self-transition.
Any events inside a state accepts and actions property:
import { setup } from "xstate";
type Event = { type: "toggle" };
type Context = { toggleValue: boolean };
const initialContext: Context = { toggleValue: false };
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
}).createMachine({
context: initialContext,
initial: "Idle",
states: {
Idle: {
on: {
toggle: {
target: "Idle",
actions: // 👈 Actions to execute when the event is triggered
},
},
},
},
});It's possible to execute multiple actions in a single event, that's why the parameter is called
actions.
assign action
When an action updates the context we need to wrap it with assign:
import { assign, setup } from "xstate";
type Event = { type: "toggle" };
type Context = { toggleValue: boolean };
const initialContext: Context = { toggleValue: false };
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
}).createMachine({
context: initialContext,
initial: "Idle",
states: {
Idle: {
on: {
toggle: {
target: "Idle",
actions: assign(
// Update context function here
),
},
},
},
},
});XState provides different helper functions to perform common actions.
assignis one of them. By callingassignwe are telling XState to update the context with the return value of the function.
assign gives access to the current context and requires to return a new context. For our toggle machine we invert toggleValue:
import { assign, setup } from "xstate";
type Event = { type: "toggle" };
type Context = { toggleValue: boolean };
const initialContext: Context = { toggleValue: false };
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
}).createMachine({
context: initialContext,
initial: "Idle",
states: {
Idle: {
on: {
toggle: {
target: "Idle",
actions: assign(
// 👇 Access to the current `context`
({ context }) => ({
// 👇 Invert `toggleValue`
toggleValue: !context.toggleValue,
})
),
},
},
},
},
});With this every time the "toggle" event is triggered from the "Idle" state, actions are executed:
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
}).createMachine({
context: initialContext,
initial: "Idle",
states: {
Idle: { // 👈 From the "Idle" state...
on: {
toggle: { // 👈 ...when the "toggle" event is triggered...
target: "Idle",
// 👇 ...then `actions` are executed
actions: assign(
({ context }) => ({
toggleValue: !context.toggleValue,
})
),
},
},
},
},
});Defining actions inside setup
Actions can be defined inside setup as well. This is useful when you want to define actions that are shared across multiple machines.
We define the action inside setup/actions, give the action a name:
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
actions: {
onToggle: // 👈 Action definition
}
}).createMachine({
context: initialContext,
initial: "Idle",
states: {
Idle: {
on: {
toggle: {
target: "Idle",
},
},
},
},
});The implementation is the same as before:
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
actions: {
onToggle: assign(({ context }) => ({ toggleValue: !context.toggleValue })),
},
}).createMachine({
context: initialContext,
initial: "Idle",
states: {
Idle: {
on: {
toggle: {
target: "Idle",
},
},
},
},
});With this we can simply reference the action by name inside the state actions property:
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
actions: {
onToggle: assign(({ context }) => ({ toggleValue: !context.toggleValue })),
},
}).createMachine({
context: initialContext,
initial: "Idle",
states: {
Idle: {
on: {
toggle: {
target: "Idle",
actions: "onToggle",
},
},
},
},
});With this every time the "toggle" event is triggered from the "Idle" state, the onToggle action is triggered:
import { assign, setup } from "xstate";
type Event = { type: "toggle" };
type Context = { toggleValue: boolean };
const initialContext: Context = { toggleValue: false };
const machine = setup({
types: {
events: {} as Event,
context: {} as Context,
},
actions: {
onToggle: assign(({ context }) => ({ toggleValue: !context.toggleValue })),
},
}).createMachine({
context: initialContext,
initial: "Idle",
states: {
Idle: { // 👈 From the "Idle" state...
on: {
toggle: { // 👈 ...when the "toggle" event is triggered...
target: "Idle",
actions: "onToggle", // 👈 ...then the `onToggle` action is executed
},
},
},
},
});