Skip to content
Version: XState v5

State machines

A state machine is a model that describes the behavior of something, for example an actor. Finite state machines describe how the state of an actor transitions to another state when an event occurs.

Benefits of state machines

State machines help build reliable and robust software. Read more about the benefits of state machines.

Creating a state machine

In XState, a state machine (referred to as a “machine”) is created using the createMachine(config) function:

import { createMachine } from 'xstate';

const feedbackMachine = createMachine({
id: 'feedback',
initial: 'question',
states: {
question: {
on: {
'feedback.good': {
target: 'thanks',
},
},
},
thanks: {
// ...
},
// ...
},
});

In this example, the machine has two states: question and thanks. The question state has a transition to the thanks state when the feedback.good event is sent to the machine:

const feedbackActor = createActor(feedbackMachine);

feedbackActor.subscribe((state) => {
console.log(state.value);
});

feedbackActor.start();
// logs 'question'

feedbackActor.send({ type: 'feedback.good' });
// logs 'thanks'

Creating actors from machines

A machine contains the logic of an actor. An actor is a running instance of the machine; in other words, it is the entity whose logic is described by the machine. Multiple actors can be created from the same machine, and each of those actors will exhibit the same behavior (reaction to received events), but they will be independent of each other and have their own states.

To create an actor, use the createActor(machine) function:

import { createActor } from 'xstate';

const feedbackActor = createActor(feedbackMachine);

feedbackActor.subscribe((state) => {
console.log(state.value);
});

feedbackActor.start();
// logs 'question'

You can also create an actor from other types of logic, such as functions, promises, and observables.

Providing implementations

Machine implementations are the language-specific code that is executed but is not directly related to the state machine’s logic (states and transitions). This includes:

  • Actions, which are fire-and-forget side-effects.
  • Actors, which are entities that can communicate with the machine actor.
  • Guards, which are conditions that determine whether a transition should be taken.
  • Delays, which specify the time before a delayed transition is taken or a delayed event is sent.

The default implementations can be provided in a setup({...}) function when creating a machine, and then you can reference those implementations using JSON-serializable strings and/or objects, such as { type: 'doSomething' }.

import { setup } from 'xstate';

const feedbackMachine = setup({
// Default implementations
actions: {
doSomething: () => {
console.log('Doing something!');
},
},
actors: {
/* ... */
},
guards: {
/* ... */
},
delays: {
/* ... */
},
}).createMachine({
entry: { type: 'doSomething' },
// ... rest of machine config
});

const feedbackActor = createActor(feedbackMachine)

feedbackActor.start();
// logs 'Doing something!'

You can override default implementations by providing implementations via machine.provide(...). This function will create a new machine with the same config, but with the provided implementations:

const customFeedbackMachine = feedbackMachine.provide({
actions: {
doSomething: () => {
console.log('Doing something else!');
},
},
});

const feedbackActor = createActor(customFeedbackMachine)

feedbackActor.start();
// logs 'Doing something else!'

Determining the next state

When you create a state machine actor, the next state is determined by the machine's current state and the event that is sent to the actor. If you want to determine the next state outside of the actor, you can use the getNextSnapshot(…) function:

import { getNextSnapshot } from 'xstate';
import { feedbackMachine } from './feedbackMachine';

const nextSnapshot = getNextSnapshot(
feedbackMachine,
feedbackMachine.resolveState({ value: 'question' }),
{ type: 'feedback.good' }
);

console.log(nextSnapshot.value);
// logs 'thanks'

You can also determine the initial state of a machine using the getInitialSnapshot(…) function:

import { getInitialSnapshot } from 'xstate';
import { feedbackMachine } from './feedbackMachine';

const initialSnapshot = getInitialSnapshot(
feedbackMachine,
// optional input
{ defaultRating: 3 }
);

console.log(initialSnapshot.value);
// logs 'question'

Specifying types

You can specify TypeScript types inside the machine config using the .types property:

import { createMachine } from 'xstate';

const feedbackMachine = createMachine({
types: {} as {
context: { feedback: string };
events: { type: 'feedback.good' } | { type: 'feedback.bad' };
actions: { type: 'logTelemetry' };
},
});

These types will be inferred throughout the machine config and in the created machine and actor so that methods such as machine.transition(...) and actor.send(...) will be type-safe.

If you are using the setup(...) function, you should provide the types in the .types property inside the setup function:

import { setup } from 'xstate';

const feedbackMachine = setup({
types: {} as {
context: { feedback: string };
events: { type: 'feedback.good' } | { type: 'feedback.bad' };
},
actions: {
logTelemetry: () => {
// TODO: implement
}
}
}).createMachine({
// ...
});

Typegen

Typegen does not yet support XState v5. However, with the setup(...) function and/or the .types property explained above, you can provide strong typing for most (if not all) of your machine.

Read more about setting up state machines.

API

Coming soon…

Machines and TypeScript

The best way to provide strong typing for your machine is to use the setup(...) function and/or the .types property.

import { setup, fromPromise } from 'xstate';

const someAction = () => {/* ... */};

const someGuard = ({ context }) => context.count <= 10;

const someActor = fromPromise(async () => {
// ...
return 42;
});

const feedbackMachine = setup({
types: {
context: {} as { count: number };
events: {} as { type: 'increment' } | { type: 'decrement' };
},
actions: {
someAction
},
guards: {
someGuard
},
actors: {
someActor
}
}).createMachine({
initial: 'counting',
states: {
counting: {
entry: { type: 'someAction' }, // strongly-typed
invoke: {
src: 'someActor', // strongly-typed
onDone: {
actions: ({ event }) => {
event.output; // strongly-typed as number
}
}
},
on: {
increment: {
guard: { type: 'someGuard' }, // strongly-typed
actions: assign({
count: ({ context }) => context.count + 1
})
}
},
}
}
});

Machine cheatsheet

Use our XState machine cheatsheet below to get started quickly.

Cheatsheet: create a machine

import { createMachine } from 'xstate';

const machine = createMachine({
initial: 'start',
states: {
start: {},
// ...
},
});

Cheatsheet: setup a machine with implementations

import { setup } from 'xstate';

const machine = setup({
actions: {
someAction: () => {/* ... */}
},
guards: {
someGuard: ({ context }) => context.count <= 10
},
actors: {
someActor: fromPromise(async () => {/* ... */})
},
delays: {
someDelay: () => 1000
}
}).createMachine({
// ... Rest of machine config
})

Cheatsheet: provide implementations

import { createMachine } from 'xstate';
import { someMachine } from './someMachine'

const machineWithImpls = someMachine.provide({
actions: {
/* ... */
},
actors: {
/* ... */
},
guards: {
/* ... */
},
delays: {
/* ... */
},
});