A small inspect
function to log events to the browser console, when you expand a group you see a log of the full event object.
const [state, send, actor] = useActor(myMachine, {
inspect: logInspectionEvent,
});
Adds colours to the browser console output, see MDN: Styling console output.
You'll notice type errors such as ts: Property 'id' does not exist on type 'ActorRefLike'
– but they can have an ID.
import type { InspectionEvent, StateValue } from 'xstate';
const isVisibleByEventName = {
'@xstate.action': true,
'@xstate.actor': false,
'@xstate.event': true,
'@xstate.microstep': true,
'@xstate.snapshot': false,
'xstate.emit': true,
'xstate.sendTo': true,
'xstate.spawnChild': true,
'xstate.stopChild': true,
} as const;
// taken from https://tailscan.com/colors
const stylesByName = {
action: 'color:#a78bfa', // violet 400
spawn: 'color:#a3e635', // green 400
stop: 'color:#f87171', // red 400
actor: 'color:inherit',
arrow: 'color:inherit',
event: 'color:#38bdf8', // sky 400
internal: 'color:#94a3b8', // slate 400
snapshot: 'color:inherit',
} as const;
export function logInspectionEvent(event: InspectionEvent): void {
if (isVisibleByEventName[event.type] === false) return;
switch (event.type) {
case '@xstate.snapshot': {
logWithColor(event, [
['internal', event.type],
['snapshot', event.actorRef?.id],
]);
break;
}
case '@xstate.event': {
if (event.sourceRef?.id && event.actorRef?.id) {
logWithColor(event, [
['internal', event.type],
['event', event.event?.type],
['actor', event.sourceRef?.id],
['internal', '➤'],
['actor', event.actorRef?.id],
]);
} else {
logWithColor(event, [
['internal', event.type],
['event', event.event?.type],
['actor', event.actorRef?.id],
]);
}
break;
}
case '@xstate.actor': {
const actorId = event.actorRef?.id;
logWithColor(event, [
['internal', event.type],
['actor', actorId],
]);
break;
}
case '@xstate.microstep': {
const stateNotation = getStateNotation(event.snapshot?.value).sort().join('\n ');
logWithColor(event, [
['internal', event.type],
['event', event.event?.type],
['actor', event.actorRef?.id],
['internal', `\n ${stateNotation}`],
]);
break;
}
case '@xstate.action': {
if (isVisibleByEventName[event.action?.type] === false) return;
switch (event.action?.type) {
case 'xstate.emit':
logWithColor(event, [
['internal', event.type],
['action', event.action?.type],
['event', event.action.params?.event?.type],
['actor', event.actorRef?.id],
]);
break;
case 'xstate.stopChild':
logWithColor(event, [
['internal', event.type],
['stop', event.action?.type],
['actor', event.actorRef?.id],
['arrow', '➤'],
['actor', event.action.params?.src],
]);
break;
case 'xstate.spawnChild':
logWithColor(event, [
['internal', event.type],
['spawn', event.action?.type],
['actor', event.actorRef?.id],
['arrow', '➤'],
['actor', event.action.params?.src],
]);
break;
case 'xstate.sendTo':
logWithColor(event, [
['internal', event.type],
['action', event.action?.type],
['event', event.action.params?.event?.type],
['actor', event.actorRef?.id],
['arrow', '➤'],
['actor', event.action.params?.to?.id],
]);
break;
default:
logWithColor(event, [
['internal', event.type],
['action', event.action?.type],
['actor', event.actorRef?.id],
]);
}
break;
}
}
}
function logWithColor(event: InspectionEvent, values: [styleName: Exclude<keyof typeof stylesByName, number | symbol>, value: string][]): void {
console.groupCollapsed(values.map(([_, value]) => `%c${value}`).join(' '), ...values.map(([styleName]) => stylesByName[styleName]));
console.log(event);
console.groupEnd();
}
function getStateNotation(stateValue: StateValue): string[] {
if (typeof stateValue === 'string') {
return [stateValue];
}
const valueKeys = Object.keys(stateValue);
return valueKeys.concat(...valueKeys.map(key => getStateNotation(stateValue[key]!).map(s => `${key}.${s}`)));
}