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. This includes:
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!'
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.
API
Coming soon… Link to API
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.
Create a machine
import { createMachine } from 'xstate';
const machine = createMachine({
initial: 'start',
states: {
start: {},
// ...
},
});
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
})
Provide implementations
import { createMachine } from 'xstate';
import { someMachine } from './someMachine'
const machineWithImpls = someMachine.provide({
actions: {
/* ... */
},
actors: {
/* ... */
},
guards: {
/* ... */
},
delays: {
/* ... */
},
});