Context, Guards, & Actions

Manage quantitative data alongside finite states using Context, and enforce strict business logic with Guards.

State Management9 min readConcept 18 of 22

The Extended State

While Finite States are perfect for discrete statuses (idle, loading), they cannot model continuous quantitative data like an email string, a counter, or a user profile object.

XState solves this with **Context**, also known as the extended state. Context is a strongly typed data object that persists inside the actor, living alongside the active finite state node.

Preventing State Node Explosion

If you tried to model a user's email input using purely finite states, you would need millions of state nodes (one for every possible string permutation).

Context prevents state explosion. It allows the machine to hold infinite data values without adding any new state nodes to the blueprint.

Actions and Guards

You update Context using **Actions**. The assign() action is a pure, side-effect function that executes synchronously during a transition to immutably merge new data into the context.

You restrict transitions using **Guards**. A guard is a pure predicate function ((args) => boolean). If a guard returns false during an event, the transition is blocked, preventing the machine from entering an invalid state.

Interactive Lab: The Guarded Form

Use the top toolbar to interact with the Multi-Step Form machine. Try clicking 'Next Step' while the email is invalid to see the Guard block the transition and gamify the error state!

const checkoutMachine = setup({ types: { context: {} as { email: string; attempts: number }, }, guards: { isValidEmail: ({ context }) => context.email.includes('@') }, actions: { assignEmail: assign(({ event }) => ({ email: event.value })) } }).createMachine({ initial: 'step1_email', context: { email: '', attempts: 0 }, states: { step1_email: { on: { TYPE_EMAIL: { actions: ['assignEmail'] }, SUBMIT: [ { guard: 'isValidEmail', target: 'step2_payment' }, { actions: ['incrementAttempts'] } ] } }, step2_payment: { /* ... */ } } });
Checkout

Step 1: Contact Info

Awaiting input...

Step 1 Complete

Context stores the email string, while the Guard strictly blocks the transition to Step 2 if the email is invalid.

Check yourself

Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.

  1. 1In XState v5, if an event triggers a transition that has both a guard and an `assign()` action, which evaluates first?

Remember this

  • Context stores arbitrary data alongside finite states to prevent state explosion.
  • Actions (like assign()) execute fire-and-forget side effects during transitions.
  • Guards are predicate functions that conditionally block or allow transitions.
  • Guards always evaluate against the current context before any actions run.

Done with this concept?

Mark it complete to track your progress. No login needed.