Claude Code Plugins

Community-maintained marketplace

Feedback

Expert guidance for XState state machines, actor model patterns, React integration, and statecharts. Use when implementing state machines, managing complex application state, using the actor model for concurrent state management, integrating XState with React components, or debugging machines.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name xstate
description Expert guidance for XState state machines, actor model patterns, React integration, and statecharts. Use when implementing state machines, managing complex application state, using the actor model for concurrent state management, integrating XState with React components, or debugging machines.

XState Skill

Expert guidance for using XState state machines and the actor model in JavaScript, TypeScript, and React projects.

When to Use This Skill

Invoke this skill when:

  • Implementing state machines or statecharts
  • Managing complex application state with XState
  • Using the actor model for concurrent state management
  • Integrating XState with React components
  • Debugging or visualizing state machines

Core Principles

  • Model behavior as finite state machines
  • Use explicit transitions over implicit state changes
  • Leverage guards for conditional transitions
  • Use actions for side effects
  • Keep machines pure and testable
  • Keep context serializable - avoid storing object instances (improves Stately Inspector compatibility)
  • Store only metadata in context, create disposable instances on-demand in invoke input functions

Machine Patterns

Basic Machine Structure

Context Design:

  • Store metadata and primitives (numbers, strings, enums, simple objects)
  • Avoid storing object instances (better serialization and debugging)
  • Keep context serializable for Stately Inspector compatibility

State Hierarchy

Final States Pattern: Nested states with sibling final state for sequential flows. Avoids brittle #rootMachine.childState targeting across scope boundaries:

ParentState: {
  initial: 'Step1',
  states: {
    Step1: {
      on: { 'Next step': 'Step2' }
    },
    Step2: {
      on: { 'Complete': 'Done' }
    },
    Done: {
      type: 'final'
    }
  },
  onDone: {
    target: 'NextParentState'
  }
}

Actions and Side Effects

Parameterized Actions: Parameterized actions for reusable logic with different values. Params can be dynamic (functions) and help TypeScript type event properties:

actions: {
  updatePosition: assign((_, params: { x: number; y: number }) => ({
    position: { x: params.x, y: params.y }
  }))
}

// Static params
entry: { type: 'updatePosition', params: { x: 100, y: 200 } }

// Dynamic params from event (TypeScript now knows event shape)
on: {
  'Click': {
    actions: {
      type: 'updatePosition',
      params: ({ event }) => ({ x: event.clientX, y: event.clientY })
    }
  }
}

Entry Actions for Preparation: Entry actions prepare state before async operations:

Moving: {
  entry: 'calculateMetadata', // Prepare before invoke
  invoke: {
    src: 'animationActor',
    input: ({ context }) => ({
      // Use prepared metadata
      duration: context.calculatedDuration
    })
  }
}

Guards and Conditions

Parameterized Guards: Parameterized guards for reusable conditional logic. Params can be dynamic (functions) and help TypeScript type event properties:

guards: {
  isWithinBounds: (_, params: { x: number; y: number; bounds: Rect }) =>
    params.x >= params.bounds.left && params.x <= params.bounds.right &&
    params.y >= params.bounds.top && params.y <= params.bounds.bottom
}

// Static params
guard: { type: 'isWithinBounds', params: { x: 10, y: 20, bounds: rect } }

// Dynamic params from event (TypeScript now knows event shape)
on: {
  'Mouse move': {
    guard: {
      type: 'isWithinBounds',
      params: ({ event, context }) => ({
        x: event.clientX,
        y: event.clientY,
        bounds: context.targetBounds
      })
    },
    target: 'Inside'
  }
}

Services and Invocations

Disposable Instances Pattern: Create instances in invoke.input, not context. Ideal for ephemeral objects that only exist during actor lifetime.

// ❌ Anti-pattern: Storing instance in context
context: {
  animationInstance: AnimationObject | null; // Hard to serialize, memory leaks
}

// ✅ Better: Store only metadata in context
context: {
  duration: number;
  speed: number;
  target: Position;
}

// Create instance on-demand in invoke
Processing: {
  entry: 'calculateMetadata', // Prepare metadata first
  invoke: {
    src: 'processingActor',
    input: ({ context }) => {
      // Create disposable instance here
      const instance = new ProcessingObject({
        duration: context.duration,
        speed: context.speed,
        target: context.target
      });

      return { instance, config: context };
    },
    onDone: { target: 'Complete' }
  }
}

Benefits: Auto GC, better serialization, Stately Inspector compatible, no cleanup, prevents leaks.

Event Narrowing with assertEvent: assertEvent in invoke input narrows event types:

invoke: {
  src: 'dataActor',
  input: ({ event }) => {
    assertEvent(event, 'Fetch data');
    // TypeScript now knows event.data exists
    return { id: event.data.id };
  }
}

TypeScript Integration

React Integration

Using @xstate/react

Component Architecture

State-Driven UI

Use Tags for Multiple State Checks: Use tags instead of multiple state.matches() calls for OR logic:

// ❌ Verbose: Multiple matches
if (state.matches('loading') || state.matches('submitting') || state.matches('validating')) {
  return <Spinner />;
}

// ✅ Better: Use tags
// In machine definition:
states: {
  loading: { tags: 'busy' },
  submitting: { tags: 'busy' },
  validating: { tags: 'busy' }
}

// In component:
if (state.hasTag('busy')) {
  return <Spinner />;
}

Actor Model

Passing Parent Actor Reference: Child actors receive and type parent actor ref for bidirectional communication:

// Parent machine
const parentMachine = setup({
	actors: {
		childActor: childMachine,
	},
}).createMachine({
	// ...
	invoke: {
		src: 'childActor',
		input: ({ self }) => ({
			parentRef: self, // Pass parent reference to child
		}),
	},
});

// Child machine
const childMachine = setup({
	types: {
		input: {} as {
			parentRef: ActorRefFrom<typeof parentMachine>; // Type the parent ref
		},
	},
}).createMachine({
	// Child can now send events to parent
	entry: ({ input }) => {
		input.parentRef.send({ type: 'Child ready' });
	},
});

Testing

Visualization and Debugging

Common Patterns and Recipes

Anti-Patterns to Avoid

File Organization

Naming Conventions

State Machine Configuration:

  • State names: Title Case (e.g., Idle, Loading Data, Processing Complete)
  • Event types: Sentence case (e.g., Submit form, Data loaded, Cancel operation)
  • Guard types: lowercase with spaces, natural language (e.g., if something meets this condition, if user is authenticated)
  • Action types: camelCase (e.g., loadData, updateContext, sendNotification)
  • Delay names: camelCase (e.g., retryDelay, debounceTimeout, pollingInterval)
  • Machine names: camelCase with "Machine" suffix (e.g., authMachine, formMachine)

Resources and References

Official Documentation:

Learning Resources:


📥 Raw Ideas & Notes (Work in Progress)

Unsorted Ideas