ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

Frontend State Machine Patterns

Model complex UI state with finite state machines. Covers XState fundamentals, statechart theory, managing multi-step forms, modal flows, and the patterns that eliminate impossible states by making valid transitions explicit.

Complex UI state is the root cause of most frontend bugs. A modal that can be open and loading and errored simultaneously. A form wizard where the user can somehow reach step 3 without completing step 1. A video player that is playing and paused at the same time. State machines eliminate these impossible states by explicitly defining which states exist and which transitions are allowed.


State Machine Fundamentals

Boolean state explosion:

  Traditional approach (4 booleans = 16 possible states):
    isLoading: true/false
    isError: true/false
    isSuccess: true/false
    isEmpty: true/false
    
    Possible states: 2⁴ = 16
    Valid states: 4
    Impossible states: 12 (isLoading AND isError AND isSuccess = ???)
    
    This is the source of 80% of UI bugs.

State machine approach (1 enum = 4 explicit states):
  state: "idle" | "loading" | "success" | "error"
  
  Possible states: 4
  Valid states: 4
  Impossible states: 0

Transitions:
  idle → loading (user clicks submit)
  loading → success (data received)
  loading → error (request failed)
  error → loading (user retries)
  success → idle (user starts over)
  
  NOT ALLOWED:
  idle → success (cannot succeed without loading)
  error → success (cannot succeed without retrying)
  success → error (cannot fail after succeeding)

XState Implementation

import { createMachine, assign } from 'xstate';

// Multi-step form wizard as a state machine
const formWizardMachine = createMachine({
  id: 'formWizard',
  initial: 'personalInfo',
  context: {
    personalInfo: {},
    address: {},
    payment: {},
    errors: [],
  },
  states: {
    personalInfo: {
      on: {
        NEXT: {
          target: 'address',
          guard: 'isPersonalInfoValid',
          actions: 'savePersonalInfo',
        },
      },
    },
    address: {
      on: {
        NEXT: {
          target: 'payment',
          guard: 'isAddressValid',
          actions: 'saveAddress',
        },
        BACK: 'personalInfo',
      },
    },
    payment: {
      on: {
        SUBMIT: {
          target: 'submitting',
          guard: 'isPaymentValid',
          actions: 'savePayment',
        },
        BACK: 'address',
      },
    },
    submitting: {
      invoke: {
        src: 'submitForm',
        onDone: 'success',
        onError: {
          target: 'error',
          actions: assign({
            errors: (_, event) => [event.data.message],
          }),
        },
      },
    },
    success: {
      type: 'final',
    },
    error: {
      on: {
        RETRY: 'submitting',
        EDIT: 'personalInfo',
      },
    },
  },
});

// Usage in React
function FormWizard() {
  const [state, send] = useMachine(formWizardMachine);
  
  return (
    <div>
      {state.matches('personalInfo') && (
        <PersonalInfoStep onNext={() => send('NEXT')} />
      )}
      {state.matches('address') && (
        <AddressStep 
          onNext={() => send('NEXT')} 
          onBack={() => send('BACK')} 
        />
      )}
      {state.matches('submitting') && <LoadingSpinner />}
      {state.matches('success') && <SuccessMessage />}
      {state.matches('error') && (
        <ErrorMessage 
          errors={state.context.errors}
          onRetry={() => send('RETRY')}
        />
      )}
    </div>
  );
}

Anti-Patterns

Anti-PatternConsequenceFix
Boolean state flagsImpossible states, race conditionsState machines with explicit states
State machine for simple toggleOver-engineeringUse state machines for 3+ states with complex transitions
Missing transition guardsInvalid state changes possibleGuards validate preconditions for each transition
State machine without visualizationHard to understand complex flowsUse XState visualizer to generate state diagrams
Business logic in componentsLogic duplicated, hard to testBusiness logic in machine, components are pure renderers

State machines are not about replacing all state management — they are about protecting the most complex state transitions from impossible combinations. Any UI flow with more than two states, conditional transitions, and error handling benefits from a state machine.

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →