Let's go back to where we started, Ben. We had four named states — idle, saving, failed and saved. The impossible combinations were gone.
Then we connected them with events. SAVE only fires from idle. SUCCESS and FAIL only fire from saving. RETRY from failed, DONE from saved. Each event locked to where it makes sense.
Then we needed to carry data — the retry count, the last error message. So we added context: a backpack that travels with the machine.
Then we needed a condition: RETRY should only fire under the retry limit. So we put a guard on the door — the rule lives on the machine, not buried in a handler.
And every transition that triggers an effect declares it right there — startRequest, clearError, incrementRetries — each one on the edge that owns it.
Ben
John... this is beautiful. Everything in one place — no handler to update every time we add a caller, no effects to remember, easy to test. Can we see it flow? Start to finish?
Sure Ben — we just need to create an actor, subscribe to it... and then, just send events!
Watch it run. Save. The machine fires — and it fails. The guard checks the count — still under the limit — so the retry goes through and fires. This time it succeeds. The whole workflow, one graph.
That was a long run, Ben. We started with two useState booleans and a hidden bug — and now we have this beautiful state machine. Impossible states: impossible. Impossible transitions: impossible. Guards, effects, context — all declared right on the machine. And we know what an actor is — we can run as many as we want, each independent.
George
Too late, gentlemen — I'm closing the office! Oh, and Ben: the checkout flow keeps double-charging when people click pay twice. I need it fixed by Friday.
Ok George, just let us grab our things. Beers on me, Ben — do you already know how you'll solve that double-charge problem?
Ben
Hmm... a state machine?