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