Skip to main content

What is a state?

A state represents a specific mode or condition that your application can be in at any given time. In XState, a machine can only be in one state at a time (unless using parallel states).

Defining states

States are defined in the states property of a machine configuration:
import { createMachine } from 'xstate';

const machine = createMachine({
  id: 'door',
  initial: 'closed',
  states: {
    closed: {
      on: {
        OPEN: 'opened'
      }
    },
    opened: {
      on: {
        CLOSE: 'closed'
      }
    }
  }
});

Initial state

Every state machine must have an initial state, specified with the initial property:
const machine = createMachine({
  initial: 'idle', // Required
  states: {
    idle: {},
    loading: {},
    success: {},
    error: {}
  }
});
Forgetting to specify an initial state will cause an error.

State values

The current state is represented by a state value, which can be:

Simple state value

A string representing the current state:
const snapshot = actor.getSnapshot();
console.log(snapshot.value); // 'idle'

Nested state value

An object for hierarchical states:
const machine = createMachine({
  initial: 'loading',
  states: {
    loading: {
      initial: 'pending',
      states: {
        pending: {},
        complete: {}
      }
    },
    loaded: {}
  }
});

// State value: { loading: 'pending' }

Parallel state value

An object with multiple active states:
const machine = createMachine({
  type: 'parallel',
  states: {
    mode: {
      initial: 'light',
      states: { light: {}, dark: {} }
    },
    size: {
      initial: 'medium',
      states: { small: {}, medium: {}, large: {} }
    }
  }
});

// State value: { mode: 'light', size: 'medium' }

State node properties

Entry actions

Actions executed when entering a state:
const machine = createMachine({
  initial: 'idle',
  states: {
    idle: {},
    loading: {
      entry: () => console.log('Started loading')
    }
  }
});

Exit actions

Actions executed when leaving a state:
const machine = createMachine({
  initial: 'idle',
  states: {
    loading: {
      entry: () => console.log('Started loading'),
      exit: () => console.log('Stopped loading')
    },
    success: {}
  }
});

Description

A description of what the state represents:
const machine = createMachine({
  initial: 'idle',
  states: {
    idle: {
      description: 'Waiting for user input'
    },
    loading: {
      description: 'Fetching data from API'
    }
  }
});

Tags

Tags for categorizing states:
const machine = createMachine({
  initial: 'idle',
  states: {
    idle: {
      tags: ['visible']
    },
    loading: {
      tags: ['visible', 'pending']
    },
    error: {
      tags: ['visible', 'failed']
    }
  }
});

const snapshot = actor.getSnapshot();
if (snapshot.hasTag('pending')) {
  console.log('Operation in progress');
}

Final states

Final states indicate that a state machine has completed:
const machine = createMachine({
  initial: 'active',
  states: {
    active: {
      on: {
        COMPLETE: 'done'
      }
    },
    done: {
      type: 'final'
    }
  }
});

const actor = createActor(machine);
actor.start();

console.log(actor.getSnapshot().status); // 'active'

actor.send({ type: 'COMPLETE' });
console.log(actor.getSnapshot().status); // 'done'
When a state machine reaches a final state, it automatically stops and cannot receive more events.

Checking states

Using matches()

Check if the current state matches a value:
const snapshot = actor.getSnapshot();

if (snapshot.matches('loading')) {
  console.log('Currently loading');
}

// For nested states
if (snapshot.matches({ loading: 'pending' })) {
  console.log('Loading is pending');
}

Using hasTag()

Check if the current state has a tag:
if (snapshot.hasTag('pending')) {
  // Show loading spinner
}

Using state value

Directly access the state value:
const value = snapshot.value;

switch (value) {
  case 'idle':
    console.log('Ready');
    break;
  case 'loading':
    console.log('Loading...');
    break;
  case 'success':
    console.log('Success!');
    break;
}

Meta data

Attach metadata to states:
const machine = createMachine({
  initial: 'idle',
  states: {
    idle: {
      meta: {
        message: 'Waiting for input'
      }
    },
    loading: {
      meta: {
        message: 'Loading data',
        progress: 0
      }
    }
  }
});

const snapshot = actor.getSnapshot();
const meta = snapshot.getMeta();
console.log(meta['idle']?.message); // 'Waiting for input'

State transitions

Define how states transition in response to events:
const machine = createMachine({
  initial: 'idle',
  states: {
    idle: {
      on: {
        FETCH: 'loading'
      }
    },
    loading: {
      on: {
        SUCCESS: 'success',
        ERROR: 'error'
      }
    },
    success: {
      on: {
        RETRY: 'loading'
      }
    },
    error: {
      on: {
        RETRY: 'loading'
      }
    }
  }
});

Best practices

Use descriptive state names that clearly indicate what the system is doing, like loadingUserData instead of loading.
Keep states at the same level of abstraction. Don’t mix high-level states like authenticated with low-level states like buttonPressed.
Use tags to categorize states that share common behaviors or UI requirements.

Next steps

Transitions

Learn about state transitions

Context

Add data to your state machines

Hierarchical states

Organize states hierarchically

Parallel states

Run multiple states simultaneously