# Models
In XState, you can model a machine's context
and events
externally by using createModel(...)
. This provides a convenient way to strongly type context
and events
, as well as helpers for event creation, assignment and other implementation details in the future.
Using createModel(...)
is completely optional, and is meant to improve the developer experience. The main reasons for using it are:
- Separating and organizing
context
andevents
in a strongly-typed way - Preventing typing issues with
assign(...)
- Specifying event creators for easier and safer event creation
- Potentially sharing the model with other machines
- Future developer experience improvements, such as specifying actions, guards, etc.
# createModel(...)
The createModel(...)
function takes
Argument | Type | Description |
---|---|---|
initialContext | object | The initial context value |
creators (optional) | object | An object containing various event creators |
The creators
object includes the following properties:
Argument | Type | Description |
---|---|---|
events | object | An object containing event creators |
The keys of the creators.events
object are event types, and the values are functions that accept any number of arguments and return the event payload.
# Modeling context
Since the model defines the machine's context
, the model can be used within the machine definition to set its initial context
with model.initialContext
and to update the machine's context
with model.assign
.
The model.assign
function is typed to the shape of the model's context
, making it a convenient and type-safe replacement for the assign
action.
import { createModel } from 'xstate/lib/model';
const userModel = createModel({
name: 'Someone',
age: 0
});
// ...
const machine = userModel.createMachine({
context: userModel.initialContext,
// ...
entry: userModel.assign({ name: '' })
});
# Modeling events
Modeling machine events in a model gives two benefits:
- Events can be created by calling
model.events.eventName(...)
- Provides type information to the machine definition, providing event-specific type safety for action definitions
import { createModel } from 'xstate/lib/model';
const userModel = createModel(
// Initial context
{
name: 'David',
age: 30
},
{
// Event creators
events: {
updateName: (value) => ({ value }),
updateAge: (value) => ({ value }),
anotherEvent: () => ({}) // no payload
}
}
);
const machine = userModel.createMachine(
{
context: userModel.initialContext,
initial: 'active',
states: {
active: {
on: {
updateName: {
actions: userModel.assign({
name: (_, event) => event.value
})
},
updateAge: {
actions: 'assignAge'
}
}
}
}
},
{
actions: {
assignAge: userModel.assign({
age: (_, event) => event.value // inferred
})
}
}
);
// This sends the following event:
// {
// type: 'updateName',
// value: 'David'
// }
const nextState = machine.transition(
undefined,
userModel.events.updateName('David')
);
# TypeScript
The createModel(...)
function infers the following types:
context
is inferred from the first argument increateModel(initialContext, creators)
events
is inferred fromcreators.events
increateModel(initialContext, creators)
import { createModel } from 'xstate/lib/model';
const userModel = createModel(
{
name: 'David', // inferred as `string`
age: 30, // inferred as `number`
friends: [] as string[] // explicit type
},
{
events: {
updateName: (value: string) => ({ value }),
updateAge: (value: number) => ({ value }),
anotherEvent: () => ({}) // no payload
}
}
);
// Context inferred as:
// {
// name: string;
// age: number;
// friends: string[];
// }
// Events inferred as:
// | { type: 'updateName'; value: string; }
// | { type: 'updateAge'; value: number; }
// | { type: 'anotherEvent'; }
# Creating a machine from a model
Instead of specifying the type of context
and event
explicitly as type parameters in createMachine<TContext, TEvent>(...)
, the model.createMachine(...)
method should be used:
const machine = userModel.createMachine({
context: userModel.initialContext,
initial: 'active',
states: {
active: {
on: {
updateName: {
actions: userModel.assign({
name: (_, event) => event.value // inferred
})
}
}
}
}
});
# Narrowing assign event types
When an assign()
action is referenced in options.actions
, you can narrow the event type that the action accepts in the 2nd argument of model.assign(assignments, eventType)
:
const assignAge = userModel.assign(
{
// The `event.type` here is restricted to "updateAge"
age: (_, event) => event.value // inferred as `number`
},
'updateAge' // Restricts the `event` allowed by the "assignAge" action
);
const machine = userModel.createMachine({
context: userModel.initialContext,
initial: 'active',
states: {
active: {
on: {
updateAge: {
actions: assignAge
}
}
}
}
});
WARNING
Assign actions with narrowed event types cannot be placed inside the actions: {...}
property of machine options in createMachine(configuration, options)
. This is because actions in options.actions
should be assumed to potentially receive any event, even if the machine configuration suggests otherwise.
# Extracting types from model
Since 4.22.1
You can extract context
and event
types from a model using the ContextFrom<T>
and EventFrom<T>
types:
import { ContextFrom, EventFrom } from 'xstate';
import { createModel } from 'xstate/lib/model';
const someModel = createModel(
{
/* ... */
},
{
events: {
/* ... */
}
}
);
type SomeContext = ContextFrom<typeof someModel>;
type SomeEvent = EventFrom<typeof someModel>;
← Context Activities →