Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save JamieMason/c78e27f4281049a6d9b72f6cf9782fb8 to your computer and use it in GitHub Desktop.
Save JamieMason/c78e27f4281049a6d9b72f6cf9782fb8 to your computer and use it in GitHub Desktop.
Log XState inspector events to the Browser Console

Log XState inspector events to the Browser Console

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.

Screenshot

Screenshot

Usage

const [state, send, actor] = useActor(myMachine, {
  inspect: logInspectionEvent,
});

Implementation

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}`)));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment