Inspector
@xstate/inspect
enables you to inspect and manipulate machines while they’re running in your app. Check out the @xstate/inspect package on GitHub.
Currently, @xstate/inspect
only works in frontend applications , but we’re working on a version that can inspect machines running in Node.
To see it running right now, here are our CodeSandbox templates:
Quickstart
- Install with npm or yarn:
bash
npm install @xstate/inspect# or yarn add @xstate/inspect
bash
npm install @xstate/inspect# or yarn add @xstate/inspect
- Import @xstate/inspect at the beginning of your project before any other code is called:
ts
import {inspect } from '@xstate/inspect';inspect ({// options// url: 'https://stately.ai/viz?inspect', // (default)iframe : false, // open in new window});
ts
import {inspect } from '@xstate/inspect';inspect ({// options// url: 'https://stately.ai/viz?inspect', // (default)iframe : false, // open in new window});
- Add
{ devTools: true }
to any interpreted machines you want to visualize:
ts
import {interpret } from 'xstate';import {inspect } from '@xstate/inspect';constactor =interpret (someMachine , {devTools : true,}).start ();
ts
import {interpret } from 'xstate';import {inspect } from '@xstate/inspect';constactor =interpret (someMachine , {devTools : true,}).start ();
Options
js
// defaultsinspect({iframe: () => document.querySelector('iframe[data-xstate]'),url: 'https://stately.ai/viz?inspect',});// the code above does the same as:inspect();
js
// defaultsinspect({iframe: () => document.querySelector('iframe[data-xstate]'),url: 'https://stately.ai/viz?inspect',});// the code above does the same as:inspect();
You can pass several properties to the options
object, all of which are optional.
iframe
iframe
(function or iframe Element
or false
) resolves to the iframe
element to display the inspector. If set to iframe: false
, a popup window is used instead.
By default, the inspector will look for an <iframe data-xstate>
element anywhere in the document. If you want to target a custom iframe, specify it eagerly or lazily:
js
// eagerinspect({iframe: document.querySelector('iframe.some-xstate-iframe'),});
js
// eagerinspect({iframe: document.querySelector('iframe.some-xstate-iframe'),});
js
// lazyinspect({iframe: () => document.querySelector('iframe.some-xstate-iframe'),});
js
// lazyinspect({iframe: () => document.querySelector('iframe.some-xstate-iframe'),});
url
Use the url
property (string) to specify the URL of the inspector you want to connect to. By default, the inspector runs on https://stately.ai/viz?inspect
.
Disconnecting
inspect
returns a function, disconnect
, you can use to disconnect the inspector.
ts
import {inspect } from '@xstate/inspect';constinspector =inspect ();inspector ?.disconnect ();
ts
import {inspect } from '@xstate/inspect';constinspector =inspect ();inspector ?.disconnect ();
Implementation
You can implement your own inspector by creating a receiver. A receiver is an actor that receives inspector events from a source, like a parent window or a WebSocket connection:
"actor.register"
ts
{type: 'actor.register';machine: AnyStateMachine;state: AnyState;id: string;sessionId: string;parent?: string;source?: string;}
ts
{type: 'actor.register';machine: AnyStateMachine;state: AnyState;id: string;sessionId: string;parent?: string;source?: string;}
"actor.stop"
ts
{type: 'actor.stop';sessionId: string;}
ts
{type: 'actor.stop';sessionId: string;}
"actor.state"
ts
{type: 'actor.state';state: AnyState;sessionId: string;}
ts
{type: 'actor.state';state: AnyState;sessionId: string;}
"actor.event"
ts
{type: 'actor.event';event: SCXML.Event<any>;sessionId: string;}
ts
{type: 'actor.event';event: SCXML.Event<any>;sessionId: string;}
To listen to events from an inspected source, create a receiver with the appropriate create*Receiver(...)
function; for example:
js
import { createWindowReceiver } from '@xstate/inspect';const windowReceiver = createWindowReceiver(/* options? */);windowReceiver.subscribe((event) => {// here, you will receive "actor.*" eventsconsole.log(event);});
js
import { createWindowReceiver } from '@xstate/inspect';const windowReceiver = createWindowReceiver(/* options? */);windowReceiver.subscribe((event) => {// here, you will receive "actor.*" eventsconsole.log(event);});
You can also send events to the receiver:
js
// ...// This will send the event to the inspected actorwindowReceiver.send({type: 'xstate.event',event: JSON.stringify({ type: 'someEvent' }),actor: /* session ID of the actor this event is sent to */});
js
// ...// This will send the event to the inspected actorwindowReceiver.send({type: 'xstate.event',event: JSON.stringify({ type: 'someEvent' }),actor: /* session ID of the actor this event is sent to */});
The typical inspection workflow is as follows:
- The
inspect(/* ... */)
call on the client opens the inspector. For example, in a separate window or creates a WebSocket connection. - The receiver sends an
"xstate.inspecting"
event to the client. - The client sends
"actor.register"
events to the receiver. - An inspector listening to the receiver via
receiver.subscribe(...)
registers the machine,event.machine
, by itsevent.sessionId
. - The machine is rendered visually, and its current state,
event.state
, is highlighted - As the actor at the source receives events and changes state, it will send the receiver
"actor.event"
and"actor.state"
events, respectively. - The inspector can use those events to highlight the current state and keep a log of events sent to that actor.
- When the actor stops, a
"actor.stop"
event is sent to the receiver with theevent.sessionId
to identify the stopped actor.
How do I run the inspector in a NextJS app?
If you want to run the inspector in a NextJS app, you must ensure that the inspector code only runs on the client rather than the server:
js
if (typeof window !== 'undefined') {inspect({/* options */});}
js
if (typeof window !== 'undefined') {inspect({/* options */});}