Typegen
You can automatically generate intelligent typings for XState using our VS Code extension or our CLI.
How to get started with VS Code
All you need to do is to install the Stately XState extension.
How to get started with the CLI (other editors)
Install the CLI and run the xstate typegen
command with the --watch
flag.
Typegen in action
Next try to create a machine, make sure to set the schema
attributes:
ts
import {createMachine } from 'xstate';constmachine =createMachine ({schema : {context : {} as {value : string },events : {} as {type : 'FOO';value : string } | {type : 'BAR' },},initial : 'a',states : {a : {on : {FOO : {actions : 'consoleLogValue',target : 'b',},},},b : {entry : 'consoleLogValueAgain',},},});
ts
import {createMachine } from 'xstate';constmachine =createMachine ({schema : {context : {} as {value : string },events : {} as {type : 'FOO';value : string } | {type : 'BAR' },},initial : 'a',states : {a : {on : {FOO : {actions : 'consoleLogValue',target : 'b',},},},b : {entry : 'consoleLogValueAgain',},},});
- Add
tsTypes: {}
to the machine and save the file:
ts
const machine = createMachine({tsTypes: {},schema: {context: {} as { value: string },events: {} as { type: 'FOO'; value: string } | { type: 'BAR' },},initial: 'a',states: {a: {},b: {},},});
ts
const machine = createMachine({tsTypes: {},schema: {context: {} as { value: string },events: {} as { type: 'FOO'; value: string } | { type: 'BAR' },},initial: 'a',states: {a: {},b: {},},});
- The extension should automatically add a generic to the machine:
ts
const machine = createMachine({tsTypes: {} as import('./filename.typegen').Typegen0,/* ... */});
ts
const machine = createMachine({tsTypes: {} as import('./filename.typegen').Typegen0,/* ... */});
- Add a second parameter into the
createMachine
call. This second parameter is where you implement the machine’s actions, actors, guards and delays.
ts
const machine = createMachine({/* ... */},{actions: {consoleLogValue: (context, event) => {// Wow! event is typed to { type: 'FOO' }console.log(event.value);},consoleLogValueAgain: (context, event) => {// Wow! event is typed to { type: 'FOO' }console.log(event.value);},},});
ts
const machine = createMachine({/* ... */},{actions: {consoleLogValue: (context, event) => {// Wow! event is typed to { type: 'FOO' }console.log(event.value);},consoleLogValueAgain: (context, event) => {// Wow! event is typed to { type: 'FOO' }console.log(event.value);},},});
Now the events in the options are strongly typed to the events that cause the action to be triggered, including actions, guards, actors and delays.
You’ll also notice that state.matches
, tags
and other parts of the machine are now type-safe.
Typing promise actors
You can use the generated types to specify the return type of promise-based actors by using the actors
schema property:
ts
import { createMachine } from 'xstate';createMachine({schema: {// `actors` in v5services: {} as {myActor: {// The data that gets returned from the actordata: { id: string };};},},invoke: {src: 'myActor',onDone: {actions: 'consoleLogId',},},},{// `actors` in v5services: {myActor: async () => {// This return type is now type-safereturn {id: '1',};},},actions: {consoleLogId: (context, event) => {// This event type is now type-safeconsole.log(event.data.id);},},});
ts
import { createMachine } from 'xstate';createMachine({schema: {// `actors` in v5services: {} as {myActor: {// The data that gets returned from the actordata: { id: string };};},},invoke: {src: 'myActor',onDone: {actions: 'consoleLogId',},},},{// `actors` in v5services: {myActor: async () => {// This return type is now type-safereturn {id: '1',};},},actions: {consoleLogId: (context, event) => {// This event type is now type-safeconsole.log(event.data.id);},},});
Typegen best practices
Below are some recommendations to help you get the most out of using typegen.
Use named actions, guards and actors
We recommend using named actions, guards and actors instead of inline actions, guards and actors.
Named actions, actors and guards allow for:
- Better visualization with the names appearing in the statechart
- Easier-to-understand code
- Overrides in
useMachine
ormachine.withConfig
The following example is optimal:
ts
createMachine({entry: ['sayHello'],},{actions: {sayHello: () => {console.log('Hello!');},},});
ts
createMachine({entry: ['sayHello'],},{actions: {sayHello: () => {console.log('Hello!');},},});
The following example is useful but less optimal:
ts
createMachine({entry: [() => {console.log('Hello!');},],});
ts
createMachine({entry: [() => {console.log('Hello!');},],});
The generated files
We recommend you gitignore the generated files (*filename*.typegen.ts
) from your repository.
You can use the CLI to regenerate them on CI (Continuous Integration), for instance, via a postinstall script:
json
{"scripts": {"postinstall": "xstate typegen \"./src/**/*.ts?(x)\""}}
json
{"scripts": {"postinstall": "xstate typegen \"./src/**/*.ts?(x)\""}}
Don’t use enums
Enums were a common pattern used with XState TypeScript and were often used to declare state names as follows:
ts
enum States {A,B,}createMachine({initial: States.A,states: {[States.A]: {},[States.B]: {},},});
ts
enum States {A,B,}createMachine({initial: States.A,states: {[States.A]: {},[States.B]: {},},});
You can then check state.matches(States.A)
on the resulting machine, which allows for type-safe checks of state names.
With typegen, using enums is no longer necessary as all state.matches
types are type-safe. Enums are currently not supported by our static analysis tool. We’re unlikely to support enums with typegen due to the complexity they add for comparatively little gain.
Instead of enums, use typegen and rely on the strength of the type-safety provided.
Nesting typegen files
When you use typegen, you'll notice that it generates new type files. If you use VS Code we have made it easy for you to nest these files. Our extension will automatically ask you if you want to enable nesting. If you want to know more about file-nesting, you can read the blog post where we introduced nesting typegen files .
Known limitations
There are a few known limitations with typegen, which we are working to fix.
“Always” transitions and raised events
Typegen might incorrectly annotate actions, actors, guards and delays if they are called “in response” to always transitions or raised events. We are working on fixing this, both in XState and in the typegen.