Skip to content

State machines

Login moves through idle, submitting, authenticated and locked-out states. Each stage accepts different events. A machine puts those rules in one transition table, so every handler does not have to repeat them.

This guide teaches how to build, test and extend that login flow. Use a machine when the legal transitions between named stages are the problem you need to model.

(ns app.login
  (:require [re-frame.core :as rf]
            [re-frame.machines]))

(rf/defmachine login-flow
  {:initial :idle
   :states
   {:idle       {:on {:auth.login/submit :submitting}}
    :submitting {:on {:auth.login/success :authed
                      :auth.login/failure :error-shown}}
    :error-shown {:on {:auth.login/submit :submitting}}
    :authed      {}}})

(rf/reg-machine :auth.login/flow login-flow)

A submit is handled only in :idle or :error-shown. While :submitting, another submit leaves the snapshot unchanged. Guards can reject an invalid submit, and actions can describe a request or update the machine's private :data.

A registered machine is an event handler. Dispatch an ordinary event with the machine id and an inner trigger vector. At the REPL, use the demo frame from First machine:

(rf/dispatch-sync [:auth.login/flow [:auth.login/submit]] {:frame login-frame})
(rf/subscribe-once [:rf/machine :auth.login/flow] {:frame login-frame})

The live snapshot belongs to that frame. It stores the active state and private data in runtime-db. Views read it through subscriptions; actions send application changes through ordinary events and effects.

As the session grows, hierarchical states can share logout behavior across its signed-in screens. Parallel regions keep form validity and request progress active independently.

When not to use a machine

Keep ordinary values in app-db. A boolean or keyword is enough while the transitions are easy to see in a few event handlers. A table becomes useful when those checks spread or invalid combinations become possible.

Situation Prefer
A counter, list, or form field app-db and events
A single loading flag a boolean or keyword
Server fetch, cache and invalidation resources
A fixed sequence of operations chained events and effects

Where should this value live? compares the state choices across re-frame2.