Skip to content
Version: XState v4

Final states

When a machine reaches the final state, it can no longer receive any events, and anything running inside it is canceled and cleaned up. A machine can have multiple final states or no final states.

To indicate that a state node is final, set its type property to final:

import { createMachine } from 'xstate';

const machine = createMachine({
initial: 'waiting',
states: {
waiting: {
after: {
4000: 'stopped',
},
},
stopped: {
type: 'final',
},
},
});

The machine above will wait for 4 seconds and then stop.

When a machine reaches its final state, several things happen:

  • The machine stops being able to receive events
  • Any running timers/actors inside the machine are canceled and cleaned up

Final states give you the option for an idiomatic way of cleaning up your machine.

Composing parent and final states

Final states can be used with parent states to make elegant, modular statecharts.

Inside a parent state, reaching a final child state node will fire a done event back to the machine. The machine can listen for the done event by declaring onDone on the state:

import { createMachine } from 'xstate';

const machine = createMachine({
initial: 'makingCoffee',
states: {
makingCoffee: {
initial: 'boilingWater',
states: {
boilingWater: {
on: {
WATER_BOILED: {
target: 'pouringWaterThroughPod',
},
},
},
pouringWaterThroughPod: {
on: {
WATER_POURED: {
target: 'complete',
},
},
},
complete: {
type: 'final',
},
},
onDone: {
target: 'coffeeMade',
},
},
coffeeMade: {},
},
});

The machine above has two main states - makingCoffee and coffeeMade. makingCoffee has several sub-steps which end up in a complete state. When the machine reaches the complete state, it fires a done event, meaning the makingCoffee state is complete.

Because the machine is listening for onDone on makingCoffee, the machine then transitions to coffeeMade.

Using onDone can make large statecharts easier to understand. The example above clearly shows that the overall “story” of the machine is that it makes coffee, then the coffee is done. If you need to know how the coffee is made, you can dive into the details of the makingCoffee state.

onDone cannot be defined on the machine’s root

The onDone transition cannot be defined on the machine’s root because onDone is a transition on a 'done.state.*' event, and when a machine reaches its final state, it can no longer accept any events.

state.done

When running a machine, you can check state.done to find out if an actor has reached its final state.

import { createMachine, interpret } from 'xstate';

const answeringMachine = createMachine({
initial: 'unanswered',
states: {
unanswered: {
on: {
ANSWER: { target: 'answered' },
},
},
answered: {
type: 'final',
},
},
});

const actor = interpret(answeringMachine).start();

actor.send({
type: 'ANSWER',
});

actor.state.done; // true