Skip to content
Version: XState v5

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: (_, params) => {
track(params);
// tracks { response: 'good' }
},
showConfetti: () => {
// ...
}
}
})

Examples of actions:

  • Logging a message
  • Sending a message to another actor
  • Coming soon: more examples

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 }, params) => {
// 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 = createActor(
feedbackMachine.provide({
actions: {
track: ({ context, event }, params) => {
// 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 = createActor(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

You can strongly type the actions of your machine in the types.actions property of the machine config.

const machine = createMachine({
types: {} as {
actions:
| {
type: 'track';
params: {
response: string;
};
}
| { type: 'increment'; params: { value: number } };
},
// ...
entry: [
{ type: 'track', params: { response: 'good' } },
{ type: 'increment', params: { value: 1 } },
],
});

Cheatsheet

Coming soon