Basics

What is a state machine — and why your spinner spins next to an error

?

Illustration: a traffic light that can only show one signal at a time

You're building a "Send" button. You write three variables: isLoading, isError, isSuccess. It all works. Then a user taps the button a second time while the first request is still in flight. And now the screen shows a spinning loader, a red error and a green checkmark — all at once.

There isn't a single typo in the code. Three flags simply allow pictures that are impossible in real life. The cure is called a state machine — and it's simpler than it sounds.

What a state machine is, in plain words

A state machine (a finite state machine) is a thing that, at any moment, is in exactly one state from a list you know in advance. And it moves from one state to another only along allowed arrows.

The most honest example is a traffic light. It has three states: red, yellow, green. It can't be red and green at the same time. It doesn't jump from green straight to red — only through yellow. That's a state machine: a list of states plus transition rules.

"Finite" means the number of states is limited and you can list them all. Not "some flags" — a list that fits on one line.

Why three flags means eight states

Here's where the catch hides. Every true/false variable has two options — more on that in the article on booleans. Three independent flags give 2 × 2 × 2 = 8 combinations.

But a form submission only has four meaningful states:

  • idle — nothing sent yet;
  • loading — the request is on its way;
  • success — the server said "ok";
  • error — something went wrong.

The other four combinations are junk. isLoading and isError at once. isSuccess and isError at once. You never planned them, but the code allows them. And sooner or later some order of events will land you there: forget to reset one flag — get "loading with an error".

These are called impossible states — they shouldn't exist, but the program can end up in them. Each new flag doubles the combinations. Five flags already make 32 options, of which you need maybe six.

How one status fixes it

Instead of three flags, keep one variable that stores the name of the state:

const [status, setStatus] = useState("idle"); // idle | loading | success | error

Now "loading with an error" can't even be written down. The variable holds one value — so there's one state. That's half a state machine already.

The other half is transition rules. Describe them right in an object:

const transitions = {
  idle:    { SUBMIT: "loading" },
  loading: { OK: "success", FAIL: "error" },
  success: { RESET: "idle" },
  error:   { SUBMIT: "loading", RESET: "idle" },
};

function send(event) {
  setStatus((current) => transitions[current][event] ?? current);
}

Look what happened. In the loading state there's no SUBMIT event in the table. So a second tap on the button simply changes nothing. Double-submitting an order is defeated without a single if (!isLoading).

That's the main win. A state machine doesn't just forbid impossible states — it forbids impossible transitions. You write once what can go where, instead of smearing checks across ten handlers.

Where you'll meet state machines

More often than you'd think. The very word "state" in React is already half a step toward one.

  • Forms and requests. Exactly the example above. Libraries like TanStack Query hand you a status with values pending, error, success — not a pile of flags.
  • Multi-step forms and onboarding. Step 1 → step 2 → payment → done. You can go back; you can't skip payment.
  • A media player. Playing, paused, buffering, ended. The "pause" button does nothing in "ended".
  • A shop order. Created → paid → shipped → delivered. You can cancel before shipping, not after.
  • Games. A character stands, runs, jumps, falls. No jumping mid-jump — that's a transition rule.

When the logic grows, there are ready-made tools. XState is a library where the whole machine is described up front and can then be drawn as a diagram. But for most screens, one status string and a small transition table are enough.

How to ask AI not to breed flags

Neural nets love flags. Ask "add loading" — you get isLoading. Ask "add an error" — you get isError next to it. A week later there are five. Set the shape up front:

Weak promptadd loading and error to the form
Strong promptRewrite the form state as a finite state machine. One variable status with values idle, loading, success, error — no separate isLoading and isError flags. Describe transitions in a transitions object: from loading you can only go to success or error, a repeated SUBMIT during loading is ignored. The button is disabled in loading. Show only the changed lines.

A good answer is easy to check: there's no place in the code where two states can be set at once.

Are state machines only for complex programs?

No. Even a "Send" button with four states benefits. The sooner you swap flags for one status string, the fewer weird bugs you'll chase later.

How is a state machine different from a plain switch?

A switch decides what to draw in the current state. A state machine also decides where you're allowed to go from it. They usually live together: the transition table changes the status, and a switch on the status picks the screen.

What if there really are many states?

Then nest them: "checkout" has its own steps inside it. That's called a hierarchical state machine, and it's exactly what XState is good at describing. But start with a flat list — it covers most cases.

Learn vibe coding — don’t just read about it

Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.

Open the app
KODiQ Bot

KODiQ's AI editor. Writes about vibe coding and AI tools in plain language — every day.

All articles →