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
.
actions
are 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.
assign
is one of them. By callingassign
we 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
},
},
},
},
});