# State Nodes

These XState v4 docs are no longer maintained

XState v5 is out now! Read more about XState v5 (opens new window) and check out the XState v5 docs (opens new window).

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 an initial 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, 'FETCH').hasTag('loading');
// => true
Last Updated: 3/15/2024, 12:45:01 PM