Context
Statecharts represent their finite state using states
but can also handle states which are not finite. These states might be:
- Counters, which can be incremented as many times as required.
- Text inputs, where the user might enter any value.
This “infinite” state can be stored in a statechart’s context, a data store that can be updated only by the statechart itself.
You can pass a machine its context using the context
property:
const machine = createMachine({
context: {
count: 0,
},
});
Next, we’ll see how to update the context using the assign action.
Assign action​
Assigning new values to the context in XState is done through the assign
action and is the only way to change a machine’s context. Never mutate a machine’s context
externally. Every context change should happen explicitly due to an event.
The assign
action takes the context assigner, representing how values should be assigned in the current context. The assigner can be an object:
import { createMachine, assign } from 'xstate';
const machine = createMachine(
{
// adding a schema for the events will make them typesafe
schema: {
events: {} as { type: 'INCREMENT'; value: number; time: Date },
},
context: {
count: 0,
updatedAt: new Date(),
message: 'Hello World',
},
on: {
INCREMENT: {
actions: 'assignToContext',
},
},
},
{
actions: {
assignToContext: assign({
// increment the current count by the event value
count: (context, event) => context.count + event.value,
/*
* you can update multiple properties at once
* we name the context parameter `_`,
* to indicate that we don’t use it
*/
updatedAt: (_, event) => event.time,
/*
* to keep TypeScript happy,
* update using a function with the context parameter
* again we use the name `_` to indicate that the
* parameter is unused
*/
message: (_) => 'Count changed',
}),
},
}
);
Or the assigner can be a function that returns the updated state:
import { createMachine, assign } from 'xstate';
const machine = createMachine(
{
context: {
count: 0,
message: '',
},
on: {
INCREMENT: {
actions: 'assignToContext',
},
},
},
{
actions: {
assignToContext: assign((context) => {
return {
count: context.count + 1,
// assign static value to the message (no function needed)
message: 'Count changed',
};
}),
},
}
);
You can pass several assign
actions in an array and they’ll be executed sequentially:
// ...
actions: [
assign({ count: 3 }),
// context.count is now 3
assign({ count: context => context.count * 2 })
// context.count is now 6
],
// ...
Using context in actions​
When XState fires an action, the action receives several arguments. The first argument is the current context
of the machine. The second argument is the most recent event
sent to the machine.
import { createMachine } from 'xstate';
createMachine(
{
context: {
count: 0,
},
on: {
LOG_COUNT: {
actions: 'logCountToConsole',
},
},
},
{
actions: {
logCountToConsole: (context, event) => {
console.log(`Count is ${context.count}`);
console.log(event.type); // Logs 'LOG_COUNT'
},
},
}
);
Summary​
States
are used for handling your apps states which you know about in advance. Context
is a data store that you can use to store any arbitrary values. The assign
action can be used to assign values to the context, and the context can be used in any action you call.