All topics
Patternsadvanced

State Machine Pattern for UI Logic

Modeling a component's behavior as a finite set of distinct states and explicit transitions between them, instead of scattered boolean flags.

A state machine replaces a set of boolean flags with one state and defined transitions.

A state machine is like a well-designed vending machine's internal logic: it can only be in one specific mode at a time — waiting for coins, dispensing, out of stock — and it only accepts the specific inputs that are valid for its current mode, refusing to even consider a 'dispense' button press while it's still in 'waiting for coins' mode, rather than a poorly-designed machine that tracks a pile of independent switches that could theoretically all be flipped on at once, leading to nonsensical states nobody actually designed for.

Key Concepts

1
The problem it solves.
    let isLoading = false;
    let isError = false;
    let isSuccess = false;
    let data = null;
2
Four variables give sixteen combinations, of which perhaps four are legal. Nothing prevents isLoading && isError, and the bug where a stale success renders during a reload comes from exactly that.
isLoading && isError
3
One state instead.
    const machine = {
      idle:    { FETCH: 'loading' },
      loading: { SUCCESS: 'success', FAILURE: 'failure' },
      success: { FETCH: 'loading' },
      failure: { FETCH: 'loading', RETRY: 'loading' }
    };
4
    function transition(state, event) {
      return machine[state]?.[event] ?? state;   // ignore illegal events
    }
5
    let state = 'idle';
    state = transition(state, 'FETCH');     // 'loading'
    state = transition(state, 'SUCCESS');   // 'success'
    state = transition(state, 'SUCCESS');   // 'success' -- ignored
6
Illegal transitions become impossible rather than merely unlikely. A SUCCESS arriving while idle does nothing, so a late response from a cancelled request cannot put the UI into a wrong state — which is the class of bug flags do not prevent.
SUCCESS
7
Rendering becomes a switch on one value.
    switch (state) {
      case 'idle':    return <Button onClick={fetch}>Load</Button>;
      case 'loading': return <Spinner />;
      case 'success': return <List items={data} />;
      case 'failure': return <Error onRetry={retry} />;
    }
8
Every state has exactly one rendering, and there is no combination to reason about.
9
Where it pays off. Multi-step forms and wizards, media players, drag and drop, authentication flows, checkout — anything where "what can happen next" depends on where you are.
10
Where it does not. A single toggle. A machine for open/closed is ceremony.
openclosed
11
Libraries. XState adds hierarchical states, parallel regions, guards, side effects and a visualiser. The diagram it produces is worth as much as the code, because it is a specification the whole team can read.
12
The signal that you need one. Three or more booleans describing the same thing, or a bug report that says "sometimes it shows the spinner and the error together". Both mean the states are implicit and some of them are illegal.
13
What the interviewer is probing.1. "What problem does a state machine solve in UI code?" Probing: the motivation. Stalls: "It is organised." Moves up: boolean flags multiply into impossible combinations — loading and error both true — where a machine makes invalid states unrepresentable.
14
2. "How do you model loading, success and error?" Probing: the application. Stalls: "Three booleans." Moves up: one state value with explicit transitions, so the UI renders from the state rather than from a combination of flags.
15
3. "What does a transition table give you?" Probing: the benefit. Stalls: "Documentation." Moves up: every legal move is declared, so an unexpected event is ignored rather than producing an undefined state.
16
4. "When is it overkill?" Probing: judgement. Stalls: "Never." Moves up: for two states with one transition, a boolean is clearer than the machinery.