Skip to content
Version: XState v5

Events and transitions

A transition is a change from one finite state to another, triggered by an event.

An event is a signal, trigger, or message that causes a transition. When an actor receives an event, its machine will determine if there are any enabled transitions for that event in the current state. If enabled transitions exist, the machine will take them and execute their actions.

Transitions are “deterministic”; each combination of state and event always points to the same next state. When a state machine receives an event, only the active finite states are checked to see if any of them have a transition for that event. Those transitions are called enabled transitions. If there is an enabled transition, the state machine will execute the transition's actions, and then transition to the target state.

Transitions are represented by on: in a state:

import { createMachine } from 'xstate';
const feedbackMachine = createMachine({
id: 'feedback',
initial: 'question',
states: {
question: {
on: {
'feedback.good': {
target: 'thanks'
}
}
}
thanks: {}
},
});

Event objects​

In XState, events are represented by event objects with a type property and optional payload:

  • The type property is a string that represents the event type.
  • The payload is an object that contains additional data about the event.
feedbackActor.send({
// The event type
type: 'feedback.update',
// Additional payload
feedback: 'This is great!',
rating: 5,
});

Selecting transitions​

Transitions are selected by checking the deepest child states first. If the transition is enabled (i.e. if its guard passes), it will be taken. If not, the parent state will be checked, and so on.

  1. Start on the deepest active state nodes (aka atomic state nodes)
  2. If the transition is enabled (no guard or its guard evaluates to true), select it.
  3. If no transition is enabled, go up to the parent state node and repeat step 1.
  4. Finally, if no transitions are enabled, no transitions will be taken, and the state will not change.

Self-transitions​

A state can transition to itself. This is known as a self-transition, and is useful for changing context and/or executing actions without changing the finite state. You can also use self-transitions to restart a state.

Transitions between states​

Usually, transitions are between two sibling states. These transitions are defined by setting the target as the sibling state key.

const feedbackMachine = createMachine({
// ...
states: {
form: {
on: {
submit: {
// Target is the key of the sibling state
target: 'submitting',
},
},
},
submitting: {
// ...
},
},
});

Coming soon… assign example

Parent to child transitions​

When a state machine receives an event, it will first check the deepest (atomic) state to see if there is any enabled transition. If not, the parent state is checked, and so on, until the machine reaches the root state.

When you want an event to transition to a state regardless of which sibling state is active, a useful pattern is to transition from the parent state to the child state.

Coming soon… example with { on: { target: '.child' } }

Re-entering​

By default, when a state machine transitions from a parent state to the same parent state or a descendent (child or deeper), it will not re-enter the parent state; that is, it will not execute the exit and entry actions of the parent state.

If you want the parent state to be re-entered, you can set the reenter property to true. This will cause the parent state to re-enter, executing the exit and entry actions of the parent state.

Coming soon… illustration showing no re-enter vs. re-enter

Coming soon… example with { target: '.child', reenter: true }

Transitions to any state​

Sibling descendent states: { target: 'sibling.child.grandchild' }

Parent to descendent states: { target: '.child.grandchild' }

State to any state: { target: '#specificState' }

Forbidden transitions​

  • { on: { forbidden: {} } }
  • Different than omitting the transition; transition selection algorithm will stop looking
  • Same as { on: { forbidden: { target: undefined } } }

Wildcard transitions​

A wildcard transition is a transition that will match any event. The event descriptor (key of the on: {...} object) is defined using the * wildcard character as the event type:

import { createMachine } from 'xstate';

const feedbackMachine = createMachine({
initial: 'asleep',
states: {
asleep: {
on: {
// This transition will match any event
'*': { target: 'awake' },
},
},
awake: {},
},
});

Wildcard transitions are useful for:

  • handling events that are not handled by any other transition.
  • as a “catch-all” transition that handles any event in a state.

A wildcard transition has the least priority; it will only be taken if no other transitions are enabled.

Partial wildcard transitions​

A partial wildcard transition is a transition that matches any event that starts with a specific prefix. The event descriptor is defined by using the wildcard character (*) after a dot (.) as the event type:

import { createMachine } from 'xstate';

const feedbackMachine = createMachine({
initial: 'prompt',
states: {
prompt: {
on: {
// This will match any event that starts with 'feedback.':
// 'feedback.good', 'feedback.bad', etc.
'feedback.*': { target: 'form' },
},
},
form: {},
// ...
},
});

The wildcard character (*) can only be used in the suffix of an event descriptor, following a dot (.):

Valid wildcard examples​

  • âś… mouse.*: matches mouse.click, mouse.move, etc.
  • âś… mouse.click.*: matches mouse.click.left, mouse.click.right, etc.

Invalid wildcard​

  • đźš« mouse*: invalid; does not match any event.
  • đźš« mouse.*.click: invalid; * cannot be used in the middle of an event descriptor.
  • đźš« *.click: invalid; * cannot be used in the prefix of an event descriptor.
  • đźš« mouse.click*: invalid; does not match any event.
  • đźš« mouse.*.*: invalid; * cannot be used in the middle of an event descriptor.

Multiple transitions in parallel states​

Since parallel states have multiple regions that can be active at the same time, it is possible for multiple transitions to be enabled at the same time. In this case, all enabled transitions to these regions will be taken.

Multiple targets are specified as an array of strings:

Coming soon… example.

Other transitions​

Transition descriptions​

You can add a .description string to a transition to describe the transition. This is useful for explaining the purpose of the transition in the visualized state machine.

import { createMachine } from 'xstate';

const feedbackMachine = createMachine({
// ...
on: {
exit: {
description: 'Closes the feedback form',
target: '.closed',
},
},
});

Shorthands​

If the transition only specifies a target, then the string target can be used as a shorthand instead of the entire transition object:

import { createMachine } from 'xstate';

const feedbackMachine = createMachine({
initial: 'prompt',
states: {
prompt: {
on: {
// This is shorthand for:
// 'feedback': { target: 'form' }
'feedback.good': 'thanks',
},
},
thanks: {},
// ...
},
});

Using the string target shorthand is useful for quickly prototyping state machines. Generally, we recommended using the full transition object syntax as it will be consistent with all other transition objects and will be easier to add actions, guards, and other properties to the transition in the future.

TypeScript​

Transitions mainly use the event type that they are enabled by.

const machine = createMachine({
types: {} as {
events: { type: 'greet'; message: string } | { type: 'submit' };
},
// ...
on: {
greet: {
actions: ({ event }) => {
event.type; // 'greet'
event.message; // string
},
},
},
});

Cheatsheet​

Use our XState events and transitions cheatsheet below to get started quickly.

Event objects​

feedbackActor.send({
// Event type
type: 'feedback.update',
// Event payload
feedback: 'A+ would use state machines again',
rating: 5,
});

Transition targets​

const machine = createMachine({
initial: 'a',
states: {
a: {
on: {
// Sibling target
event: {
target: 'b',
},
// Sibling child target
otherEvent: {
target: 'b.c',
},
},
},
b: {
on: {
// ID target
event: {
target: '#c',
},
},
},
c: {
id: 'c',
on: {
// Child target
event: {
target: '.child',
},
},
initial: 'child',
states: {
child: {},
},
},
},
on: {
// Child target
someEvent: {
target: '.b',
},
},
});