DOCS CORE_CONCEPTS Understanding State

Understanding State

Understanding State

States are the foundation of any finite state machine. At any given time, your machine is in exactly one state. States represent the different modes or conditions your application can be in.

In traditional programming, we often use boolean flags or multiple variables to track application state. This can lead to invalid state combinations and bugs. With finite state machines, you explicitly define all possible states, making your application’s behavior predictable and easy to reason about.

Defining States

In Robot, states are created using the state function and organized within a machine:

import { createMachine, state } from 'robot3';

const machine = createMachine({
  idle: state(),
  loading: state(),
  loaded: state(),
  error: state()
});

States in Robot are declarative - you define what states exist and what transitions are possible from each state. This makes your application’s behavior predictable and easy to reason about.

Initial State

Every machine starts in an initial state. By default, this is the first state defined in your machine:

const machine = createMachine({
  idle: state(),      // This is the initial state
  active: state()
});

You can also explicitly specify the initial state:

const machine = createMachine('active', {
  idle: state(),
  active: state()     // Machine starts here
});

Final States

States with no transitions are considered final states. When a machine reaches a final state, it stays there unless externally reset:

const machine = createMachine({
  processing: state(
    transition('complete', 'finished')
  ),
  finished: state()   // Final state - no way out
});

Final states are useful for representing completed workflows, terminated processes, or end conditions in your application logic.

State Names

State names should be:

  • Descriptive: Choose names that clearly describe what the state represents
  • Unique: Each state must have a unique name within a machine
  • Verb or noun form: Use names like loading, idle, authenticated rather than isLoading, shouldLoad
// Good state names
const machine = createMachine({
  idle: state(),
  authenticating: state(),
  authenticated: state(),
  failed: state()
});

// Avoid boolean-style names
const machine = createMachine({
  isIdle: state(),           // ❌ Don't prefix with 'is'
  hasAuthenticated: state(), // ❌ Don't prefix with 'has'
  shouldRetry: state()       // ❌ Don't prefix with 'should'
});

Why States Matter

Eliminating Invalid States

Without explicit states, applications often use multiple booleans that can create impossible combinations:

// Without FSM - invalid states possible
let loading = false;
let loaded = false;
let error = false;

// What does this mean? ❌
loading = true;
loaded = true;
error = true;

With finite state machines, you can only be in one state at a time:

// With FSM - always valid ✅
const machine = createMachine({
  idle: state(),
  loading: state(),
  loaded: state(),
  error: state()
});
// Machine is in exactly ONE state

Making State Explicit

Explicit states make your code self-documenting. Instead of scattered conditional logic, your states are clearly defined:

// Without FSM - implicit state
function handleClick() {
  if (!loading && !error && data === null) {
    // What state are we in? 🤔
    fetchData();
  }
}

// With FSM - explicit state ✅
const machine = createMachine({
  idle: state(
    transition('fetch', 'loading')
  ),
  loading: state(
    transition('success', 'loaded'),
    transition('error', 'error')
  ),
  loaded: state(),
  error: state()
});

State vs Context

It’s important to distinguish between state (which mode you’re in) and context (the data associated with that state):

  • State: The current mode or phase (e.g., loading, idle, error)
  • Context: Data that persists across states (e.g., user info, error messages, counters)
const machine = createMachine({
  idle: state(
    transition('fetch', 'loading')
  ),
  loading: state(
    transition('success', 'loaded')
  ),
  loaded: state()
}, () => ({
  // This is context - data that can change
  users: [],
  errorMessage: null,
  retryCount: 0
}));

States tell you where you are in your application flow. Context tells you what data you have while you’re there.

  • Transitions - How to move between states
  • Events - What triggers state changes
  • state API - Technical reference for the state function