Actions
Actions are fire-and-forget effects. When a state machine transitions, it may execute actions. Actions occur in response to events, and are typically defined on transitions in the actions: ...
property. Anywhere you can use an action, you can also declare it as an array to express multiple actions.
Actions can also be on a states’ entry
or exit
, also as a single action or an array.
const feedbackMachine = createMachine({
// ...
states: {
// ...
question: {
on: {
'feedback.good': {
actions: [
{ type: 'track', params: { response: 'good' } }
]
}
},
exit: ['exitAction']
}
thanks: {
entry: ['showConfetti'],
}
}
}, {
actions: {
track: ({ action }) => {
track(action.params);
// tracks { response: 'good' }
},
showConfetti: () => {
// ...
}
}
})
Examples of actions:
- Logging a message
- Sending a message to another actor
- Coming soon: more examples
↓Jump to learning more about entry and exit actions in XState↓
Using actions in Stately Studio
In our video player machine, we have entry and exit actions on the Playing state. We use the entry action of playVideo to fire an effect playing the video on entry to the Playing state. We use the exit action of pauseVideo to fire an effect pausing the video when the Playing state is exited.
Add an entry action to a state
- Select the state you want to have an entry action.
- Open the State details panel from the right tool menu.
- Use the + plus icon alongside the Entry actions to add a new action.
- Use the Custom tab under Entry actions to input the type for the entry action.
- Use the Assign tab under Entry actions to assign key and assignment pairs to the entry action.
- Save the entry action using the Save button.
Add an exit action to a state
- Select the state you want to have an exit action.
- Open the State details panel from the right tool menu.
- Use the + plus icon alongside the Exit actions to add a new action.
- Use the Custom tab under Exit actions to input the type for the exit action.
- Use the Assign tab under Exit actions to assign key and assignment pairs to the exit action.
- Save the entry action using the Save button.
In our video player machine, we have entry and exit actions on the Playing state. We use the entry action of playVideo to fire an effect playing the video on entry to the Playing state. We use the exit action of pauseVideo to fire an effect pausing the video when the Playing state is exited.
Entry and exit actions
Entry actions are actions that occur on any transition that enters a state node.
Coming soon… illustation
Exit actions are actions that occur on any transition that exits a state node.
Coming soon… illustration
Entry and exit actions are defined using the entry: ...
and exit: ...
attributes on a state node. You can fire multiple entry and exit actions on a state. Top-level final states cannot have exit actions, since the machine is stopped and no further transitions can occur.
Coming soon… example
Inline actions
You can declare actions as inline functions:
import { createMachine } from 'xstate';
const feedbackMachine = createMachine({
entry: [
// Inline action
({ context, event }) => {
console.log(/* ... */);
},
],
});
Inline actions are useful for prototyping and simple cases but we generally recommended using serialized actions.
Action objects
Action objects have an action type
and an optional params
object:
- Action
type
describes the action. Actions with the same type have the same implementation. - Action
params
hold parameterized values that are relevant to the action.
const feedbackMachine = createMachine({
// ...
states: {
// ...
question: {
on: {
'feedback.good': {
actions: [
{
// Action type
type: 'track',
// Action params
params: { response: 'good' },
},
],
},
},
},
},
});
Implementing actions
You can add the implementations for named actions in the actions
property of the 2nd argument of createMachine(config, implementations)
:
const feedbackMachine = createMachine(
{
// Machine config
// ...
},
{
actions: {
track: ({ context, event }) => {
// Action implementation
// ...
},
},
},
);
These implementations can later be overridden by providing implementations in the machine.provide(implementations)
method, which creates a new machine with the same config but with the provided implementations:
const feedbackActor = interpret(
feedbackMachine.provide({
actions: {
track: ({ context, event }) => {
// Overridden action implementation
// ...
},
},
}),
);
Assign action
The assign(...)
action is a special action that assigns data to the state context. The assignments
argument in assign(assignments)
is where assignments to context are specified.
Assignments can be an object of key-value pairs where the keys are context
keys and the values are either static values or expressions that return the new value:
const countMachine = createMachine({
context: {
count: 0,
},
on: {
increment: {
actions: assign({
count: ({ context, event }) => context.count + event.value,
}),
},
},
});
const countActor = interpret(countMachine);
countActor.subscribe((state) => {
console.log(state.context.count);
});
countActor.start();
// logs 0
countActor.send({ type: 'increment', value: 3 });
// logs 3
countActor.send({ type: 'increment', value: 2 });
// logs 5
For more dynamic assignments, the assignments
argument may also be a function that returns the partial or full context
value:
const countMachine = createMachine({
context: {
count: 0,
},
on: {
increment: {
actions: assign(({ context, event }) => {
return {
count: context.count + event.value,
};
}),
},
},
});
Raise action
The raise action is a special action that raises an event that is received by the same machine. Raising an event is how a machine can “send” an event to itself:
actions: raise({ type: 'someEvent', data: 'someData' });
Internally, when an event is raised, it is placed into an “internal event queue”. After the current transition is finished, these events are processed in insertion order (first-in first-out, or FIFO). External events are only processed once all events in the internal event queue are processed.
Raised events can be dynamic:
raise(({ context, event }) => ({ type: 'SOME_EVENT' }));
Events can also be raised with a delay, which will not place them in the internal event queue, since they will not be immediately processed:
actions: raise({ type: 'someEvent' }, { delay: 1000 });
Send-to action
The sendTo(...)
action is a special action that sends an event to a specific actor.
const machine = createMachine({
on: {
transmit: {
actions: sendTo('someActor', { type: 'someEvent' }),
},
},
});
The event can be dynamic:
const machine = createMachine({
on: {
transmit: {
actions: sendTo('someActor', ({ context, event }) => {
return { type: 'someEvent', data: context.someData };
}),
},
},
});
The destination actor can be the actor ID or the actor reference itself:
const machine = createMachine({
context: ({ spawn }) => ({
someActorRef: spawn(fromPromise(/* ... */)),
}),
on: {
transmit: {
actions: sendTo(({ context }) => context.someActorRef, {
type: 'someEvent',
}),
},
},
});
Other options, such as delay
and id
, can be passed as the 3rd argument:
const machine = createMachine({
on: {
transmit: {
actions: sendTo(
'someActor',
{ type: 'someEvent' },
{
id: 'transmission',
delay: 1000,
},
),
},
},
});
Pure action
The pure(...)
action is a higher-level action that returns an array of actions to be executed, without actually executing any effects (hence the name "pure").
Example coming soon
Log action
The log(...)
action is an easy way to log messages to the console.
Example coming soon
Choose action
The choose(...)
action is a higher-level action that returns an array of actions to be executed.
Example coming soon
Cancel action
The cancel(...)
action cancels a delayed sendTo(...)
or raise(...)
action by their IDs:
import { createMachine, sendTo, cancel } from 'xstate';
const machine = createMachine({
on: {
event: {
actions: sendTo(
'someActor',
{ type: 'someEvent' },
{
id: 'someId',
delay: 1000,
},
),
},
cancelEvent: {
actions: cancel('someId'),
},
},
});
Stop action
The stop(...)
action stops a child actor. Actors can only be stopped from their parent actor:
const machine = createMachine({
context: ({ spawn }) => ({
spawnedRef: spawn(fromPromise(/* ... */), { id: 'spawnedId' }),
}),
on: {
stopById: {
actions: stop('spawnedId'),
},
stopByRef: {
actions: stop(({ context }) => context.spawnedRef),
},
},
});
Modeling
If you only need to execute actions in response to events, you can create a self-transition that only has actions: [ ... ]
defined. For example, a machine that only needs to assign to context
in transitions may look like this:
const countMachine = createMachine({
context: {
count: 0,
},
on: {
increment: {
actions: assign({
count: ({ context, event }) => context.count + event.value,
}),
},
decrement: {
actions: assign({
count: ({ context, event }) => context.count - event.value,
}),
},
},
});
Shorthands
- For simple actions, can specify action string instead of object
- Objects preferred for consistency
TypeScript
Coming soon
Cheatsheet
Coming soon