# State Nodes
A state machine contains state nodes (explained below) that collectively describe the overall state a machine can be in. In the fetchMachine
described in the next section, there are state nodes, such as:
// ...
{
states: {
// state node
idle: {
on: {
FETCH: {
target: 'pending';
}
}
}
}
}
And the overall state, which is the return value of the machine.transition()
function or the callback value of service.onTransition()
:
const nextState = fetchMachine.transition('pending', { type: 'FULFILL' });
// State {
// value: { success: 'items' },
// actions: [],
// context: undefined,
// ...
// }
# What Are State Nodes?
In XState, a state node specifies a state configuration. They are defined on the machine's states
property. Likewise, sub-state nodes are hierarchically defined on the states
property of a state node.
The state determined from machine.transition(state, event)
represents a combination of state nodes. For example, in the machine below, there's a success
state node and an items
substate node. The state value { success: 'items' }
represents the combination of those state nodes.
const fetchMachine = createMachine({
id: 'fetch',
// Initial state
initial: 'idle',
// States
states: {
idle: {
on: {
FETCH: { target: 'pending' }
}
},
pending: {
on: {
FULFILL: { target: 'success' },
REJECT: { target: 'failure' }
}
},
success: {
// Initial child state
initial: 'items',
// Child states
states: {
items: {
on: {
'ITEM.CLICK': { target: 'item' }
}
},
item: {
on: {
BACK: { target: 'items' }
}
}
}
},
failure: {
on: {
RETRY: { target: 'pending' }
}
}
}
});
# State Node Types
There are five different kinds of state nodes:
- An atomic state node has no child states. (I.e., it is a leaf node.)
- A compound state node contains one or more child
states
, and has aninitial
state, which is the key of one of those child states. - A parallel state node contains two or more child
states
, and has no initial state, since it represents being in all of its child states at the same time. - A final state node is a leaf node that represents an abstract "terminal" state.
- A history state node is an abstract node that represents resolving to its parent node's most recent shallow or deep history state.
The state node type can be explicitly defined on the state node:
const machine = createMachine({
id: 'fetch',
initial: 'idle',
states: {
idle: {
type: 'atomic',
on: {
FETCH: { target: 'pending' }
}
},
pending: {
type: 'parallel',
states: {
resource1: {
type: 'compound',
initial: 'pending',
states: {
pending: {
on: {
'FULFILL.resource1': { target: 'success' }
}
},
success: {
type: 'final'
}
}
},
resource2: {
type: 'compound',
initial: 'pending',
states: {
pending: {
on: {
'FULFILL.resource2': { target: 'success' }
}
},
success: {
type: 'final'
}
}
}
},
onDone: 'success'
},
success: {
type: 'compound',
initial: 'items',
states: {
items: {
on: {
'ITEM.CLICK': { target: 'item' }
}
},
item: {
on: {
BACK: { target: 'items' }
}
},
hist: {
type: 'history',
history: 'shallow'
}
}
}
}
});
Explicitly specifying the type
as 'atomic'
, 'compound'
, 'parallel'
, 'history'
, or 'final'
is helpful with regard to analysis and type-checking in TypeScript. However, it is only required for parallel, history, and final states.
# Transient State Nodes
A transient state node is a "pass-through" state node that immediately transitions to another state node; that is, a machine does not stay in a transient state. Transient state nodes are useful for determining which state the machine should really go to from a previous state based on conditions. They are most similar to choice pseudostates (opens new window) in UML.
The best way to define a transient state node is as an eventless state, and an always
transition. This is a transition where the first condition that evaluates to true is immediately taken.
For example, this machine's initial transient state resolves to 'morning'
, 'afternoon'
, or 'evening'
, depending on what time it is (implementation details hidden):
const timeOfDayMachine = createMachine({
id: 'timeOfDay',
initial: 'unknown',
context: {
time: undefined
},
states: {
// Transient state
unknown: {
always: [
{ target: 'morning', cond: 'isBeforeNoon' },
{ target: 'afternoon', cond: 'isBeforeSix' },
{ target: 'evening' }
]
},
morning: {},
afternoon: {},
evening: {}
}
}, {
guards: {
isBeforeNoon: // ...
isBeforeSix: // ...
}
});
const timeOfDayService = interpret(timeOfDayMachine.withContext({ time: Date.now() }))
.onTransition(state => console.log(state.value))
.start();
// => 'morning' (assuming the time is before noon)
# State Node Meta Data
Meta data, which is static data that describes relevant properties of any state node, can be specified on the .meta
property of the state node:
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
states: {
idle: {
on: {
FETCH: { target: 'loading' }
}
},
loading: {
after: {
3000: { target: 'failure.timeout' }
},
on: {
RESOLVE: { target: 'success' },
REJECT: { target: 'failure' },
TIMEOUT: { target: 'failure.timeout' } // manual timeout
},
meta: {
message: 'Loading...'
}
},
success: {
meta: {
message: 'The request succeeded!'
}
},
failure: {
initial: 'rejection',
states: {
rejection: {
meta: {
message: 'The request failed.'
}
},
timeout: {
meta: {
message: 'The request timed out.'
}
}
},
meta: {
alert: 'Uh oh.'
}
}
}
});
The current state of the machine collects the .meta
data of all of the state nodes represented by the state value, and places them on an object where:
- The keys are the state node IDs
- The values are the state node
.meta
values
See state meta data for usage and more information.
# Tags
State nodes can have tags, which are string terms that help describe the state node. Tags are metadata that can be useful in categorizing different state nodes. For example, you can signify which state nodes represent states in which data is being loaded by using a "loading"
tag, and determine if a state contains those tagged state nodes with state.hasTag(tag)
:
const machine = createMachine({
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loadingUser'
}
},
loadingUser: {
tags: ['loading']
// ...
},
loadingFriends: {
tags: ['loading']
// ...
},
editing: {
// ...
}
}
});
machine.initialState.hasTag('loading');
// => false
machine.transition(machine.initialState, { type: 'FETCH' }).hasTag('loading');
// => true