CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-xstate

Actor-based state management & orchestration for complex app logic.

Overview
Eval results
Files

actions.mddocs/

Actions

Comprehensive set of built-in actions for context assignment, event handling, actor communication, and lifecycle management. Actions are declarative and provide the building blocks for state machine behaviors.

Capabilities

Context Assignment

Updates the current context of the machine with new values, supporting both object assignment and functional updates.

/**
 * Updates the current context of the machine
 * @param assignment - Object with new context values or function returning context update
 * @returns ActionFunction that updates machine context
 */
function assign<TContext, TExpressionEvent extends EventObject>(
  assignment: 
    | Assigner<TContext, TExpressionEvent>
    | PartialAssigner<TContext, TExpressionEvent>
    | PropertyAssigner<TContext, TExpressionEvent>
): ActionFunction<TContext, TExpressionEvent>;

type Assigner<TContext, TExpressionEvent extends EventObject> = (args: {
  context: TContext;
  event: TExpressionEvent;
  spawn: Spawner;
}) => TContext;

type PartialAssigner<TContext, TExpressionEvent extends EventObject> = (args: {
  context: TContext;
  event: TExpressionEvent;
  spawn: Spawner;
}) => Partial<TContext>;

type PropertyAssigner<TContext, TExpressionEvent extends EventObject> = {
  [K in keyof TContext]?: 
    | TContext[K] 
    | ((args: { context: TContext; event: TExpressionEvent; spawn: Spawner }) => TContext[K]);
};

interface AssignAction extends ActionFunction<any, any> {
  type: "xstate.assign";
}

Usage Examples:

import { assign } from "xstate/actions";

// Object-based assignment
assign({ count: 42, name: "Updated" })

// Function-based assignment
assign(({ context, event }) => ({
  count: context.count + 1,
  lastEvent: event.type
}))

// Property-based assignment
assign({
  count: ({ context }) => context.count + 1,
  timestamp: () => Date.now()
})

// Partial assignment
assign(({ context, event }) => {
  if (event.type === "INCREMENT") {
    return { count: context.count + 1 };
  }
  return {};
})

Event Handling

Actions for raising internal events and emitting external events.

/**
 * Raises an event to the internal event queue for immediate processing
 * @param eventOrExpr - Event object or function returning event
 * @param options - Optional configuration with id and delay
 * @returns ActionFunction that raises internal events
 */
function raise<TEvent extends EventObject>(
  eventOrExpr: TEvent | ((args: ActionArgs<any, any>) => TEvent),
  options?: { id?: string; delay?: number }
): ActionFunction<any, any>;

/**
 * Emits an event to external event handlers registered on the actor
 * @param eventOrExpr - Event object or function returning event to emit
 * @returns ActionFunction that emits events to external listeners
 */
function emit<TEvent extends EventObject>(
  eventOrExpr: TEvent | ((args: ActionArgs<any, any>) => TEvent)
): ActionFunction<any, any>;

interface RaiseAction extends ActionFunction<any, any> {
  type: "xstate.raise";
}

interface EmitAction extends ActionFunction<any, any> {
  type: "xstate.emit";
}

Usage Examples:

import { raise, emit } from "xstate/actions";

// Raise internal event
raise({ type: "INTERNAL_UPDATE", data: "processed" })

// Raise with delay
raise({ type: "TIMEOUT" }, { delay: 1000 })

// Emit external event
emit({ type: "STATUS_CHANGED", status: "ready" })

// Dynamic event creation
raise(({ context, event }) => ({ 
  type: "COMPUTED", 
  result: context.value * 2 
}))

Actor Communication

Actions for sending events between actors and managing parent-child communication.

/**
 * Sends an event to a specific actor
 * @param actor - Target actor reference, string ID, or function resolving to actor
 * @param eventOrExpr - Event object or function returning event to send
 * @param options - Optional configuration with id and delay
 * @returns ActionFunction that sends events to other actors
 */
function sendTo<TTargetActor extends AnyActorRef, TEvent extends EventObject>(
  actor: TTargetActor | string | ((args: ActionArgs<any, any>) => TTargetActor | string),
  eventOrExpr: TEvent | ((args: ActionArgs<any, any>) => TEvent),
  options?: { id?: string; delay?: number }
): ActionFunction<any, any>;

/**
 * Sends an event to the parent machine
 * @param eventOrExpr - Event object or function returning event to send
 * @param options - Optional configuration with id and delay
 * @returns ActionFunction for parent communication
 */
function sendParent<TEvent extends EventObject>(
  eventOrExpr: TEvent | ((args: ActionArgs<any, any>) => TEvent),
  options?: { id?: string; delay?: number }
): ActionFunction<any, any>;

/**
 * Forwards the current event to a target actor
 * @param actor - Target actor reference, string ID, or function resolving to actor
 * @param options - Optional configuration with id and delay
 * @returns ActionFunction that forwards events without modification
 */
function forwardTo<TTargetActor extends AnyActorRef>(
  actor: TTargetActor | string | ((args: ActionArgs<any, any>) => TTargetActor | string),
  options?: { id?: string; delay?: number }
): ActionFunction<any, any>;

interface SendToAction extends ActionFunction<any, any> {
  type: "xstate.sendTo";
}

Usage Examples:

import { sendTo, sendParent, forwardTo } from "xstate/actions";

// Send to specific actor
sendTo("childActor", { type: "UPDATE", data: "new value" })

// Send with delay
sendTo("childActor", { type: "DELAYED_UPDATE" }, { delay: 2000 })

// Send to parent
sendParent({ type: "CHILD_COMPLETED", result: "success" })

// Dynamic actor resolution
sendTo(
  ({ context }) => context.targetActor,
  ({ event }) => ({ type: "FORWARDED", original: event })
)

// Forward current event
forwardTo("childActor")

Actor Lifecycle

Actions for spawning and stopping child actors.

/**
 * Spawns a child actor
 * @param src - Actor logic or string reference to actor logic
 * @param options - Optional configuration with id, systemId, input, and syncSnapshot
 * @returns ActionFunction that creates and starts child actors
 */
function spawnChild<TActorLogic extends AnyActorLogic>(
  src: TActorLogic | string,
  options?: {
    id?: string | ((args: ActionArgs<any, any>) => string);
    systemId?: string;
    input?: InputFrom<TActorLogic> | ((args: ActionArgs<any, any>) => InputFrom<TActorLogic>);
    syncSnapshot?: boolean;
  }
): ActionFunction<any, any>;

/**
 * Stops a child actor
 * @param actorRef - Actor reference, string ID, or function resolving to actor
 * @returns ActionFunction that stops and removes child actors
 */
function stopChild(
  actorRef: AnyActorRef | string | ((args: ActionArgs<any, any>) => AnyActorRef | string)
): ActionFunction<any, any>;

/**
 * Deprecated alias for stopChild
 * @deprecated Use stopChild instead
 */
function stop(
  actorRef: AnyActorRef | string | ((args: ActionArgs<any, any>) => AnyActorRef | string)
): ActionFunction<any, any>;

interface SpawnAction extends ActionFunction<any, any> {
  type: "xstate.spawnChild";
}

interface StopAction extends ActionFunction<any, any> {
  type: "xstate.stopChild";
}

Usage Examples:

import { spawnChild, stopChild } from "xstate/actions";

// Spawn child actor
spawnChild("childLogic", {
  id: "child-1",
  input: { initialValue: 100 }
})

// Dynamic spawning
spawnChild(
  ({ context }) => context.childLogic,
  {
    id: ({ event }) => `child-${event.childId}`,
    input: ({ context, event }) => ({ 
      parentContext: context,
      eventData: event.data 
    })
  }
)

// Stop child actor
stopChild("child-1")

// Dynamic stopping
stopChild(({ context }) => context.activeChild)

Execution Control

Actions for controlling action execution flow and canceling delayed actions.

/**
 * Creates an action that executes dynamically queued actions
 * @param collect - Function that receives enqueue function and action context
 * @returns ActionFunction that allows conditional action execution
 */
function enqueueActions<TContext, TExpressionEvent extends EventObject>(
  collect: (
    enqueue: {
      assign: typeof assign;
      raise: typeof raise;
      sendTo: typeof sendTo;
      sendParent: typeof sendParent;
      spawnChild: typeof spawnChild;  
      stopChild: typeof stopChild;
      cancel: typeof cancel;
      log: typeof log;
      emit: typeof emit;
    },
    params: {
      context: TContext;
      event: TExpressionEvent;
      check: (guard: any) => boolean;
      self: AnyActorRef;
    }
  ) => void
): ActionFunction<TContext, TExpressionEvent>;

/**
 * Cancels a delayed sendTo action that is waiting to be executed
 * @param sendId - ID of the sendTo action to cancel (string or function returning string)
 * @returns ActionFunction that cancels scheduled delayed actions
 */
function cancel(
  sendId: string | ((args: ActionArgs<any, any>) => string)
): ActionFunction<any, any>;

interface EnqueueActionsAction extends ActionFunction<any, any> {
  type: "xstate.enqueueActions";
}

interface CancelAction extends ActionFunction<any, any> {
  type: "xstate.cancel";
}

Usage Examples:

import { enqueueActions, cancel } from "xstate/actions";

// Dynamic action execution
enqueueActions(({ enqueue, check, context, event }) => {
  if (check("isValidUser")) {
    enqueue.assign({ lastLogin: Date.now() });
    enqueue.sendParent({ type: "USER_VALIDATED" });
  } else {
    enqueue.raise({ type: "VALIDATION_FAILED" });
  }
  
  if (context.shouldNotify) {
    enqueue.emit({ type: "LOGIN_ATTEMPT", user: context.user });
  }
})

// Cancel delayed action
cancel("delayed-notification")

// Dynamic cancellation
cancel(({ context }) => context.pendingActionId)

Debugging

Actions for logging and debugging state machine execution.

/**
 * Logs values during action execution
 * @param value - Value to log (string or function returning value)
 * @param label - Optional label for the log entry
 * @returns ActionFunction that logs to the actor's logger
 */
function log<TContext, TExpressionEvent extends EventObject>(
  value?: string | ((args: ActionArgs<TContext, TExpressionEvent>) => any),
  label?: string
): ActionFunction<TContext, TExpressionEvent>;

interface LogAction extends ActionFunction<any, any> {
  type: "xstate.log";
}

Usage Examples:

import { log } from "xstate/actions";

// Simple logging
log("State machine started")

// Dynamic logging
log(({ context, event }) => `User ${context.userId} performed ${event.type}`)

// Labeled logging
log(({ context }) => context.debugInfo, "Debug Info")

// Log context and event (default behavior)
log()

Action Types and Utilities

interface ActionArgs<TContext, TExpressionEvent extends EventObject> {
  /** Current machine context */
  context: TContext;
  /** Current event being processed */
  event: TExpressionEvent;
  /** Reference to the current actor */
  self: AnyActorRef;
  /** Function for spawning child actors */
  spawn: Spawner;
}

interface ActionFunction<TContext, TExpressionEvent extends EventObject> {
  (args: ActionArgs<TContext, TExpressionEvent>): void;
  /** Action type identifier */
  type?: string;
  /** Resolve dynamic parameters */
  resolve?: (args: ActionArgs<TContext, TExpressionEvent>) => any;
}

type Actions<TContext, TEvent extends EventObject, TAction> =
  | Action<TContext, TEvent, TAction>
  | Action<TContext, TEvent, TAction>[];

type Action<TContext, TEvent extends EventObject, TAction> =
  | TAction
  | ActionFunction<TContext, TEvent>
  | { type: string; [key: string]: any };

interface Spawner {
  <TActorLogic extends AnyActorLogic>(
    src: TActorLogic,
    options?: {
      id?: string;
      input?: InputFrom<TActorLogic>;
      syncSnapshot?: boolean;
    }
  ): ActorRefFrom<TActorLogic>;
}

type UnknownAction = Action<any, any, any>;

Best Practices

Action Composition:

// Combine multiple actions
entry: [
  assign({ status: "loading" }),
  log("Starting data fetch"),
  spawnChild("fetchLogic", { id: "fetcher" })
]

// Use enqueueActions for conditional logic
entry: enqueueActions(({ enqueue, check, context }) => {
  enqueue.assign({ startTime: Date.now() });
  
  if (check("shouldLog")) {
    enqueue.log("Process started");
  }
  
  if (context.enableNotifications) {
    enqueue.emit({ type: "PROCESS_STARTED" });
  }
})

Dynamic Parameters:

// Actions with dynamic parameters
sendTo(
  ({ context }) => context.targetActor,
  ({ event, context }) => ({
    type: "PROCESS_DATA",
    data: event.data,
    timestamp: context.currentTime
  }),
  { delay: ({ context }) => context.processingDelay }
)

Install with Tessl CLI

npx tessl i tessl/npm-xstate

docs

actions.md

actors.md

graph-utilities.md

guards.md

index.md

state-machines.md

tile.json