Flows (re-frame.alpha, experimental)

A flow declaratively derives a value at one app-db path from values at other paths, recomputed synchronously after every event (domino 3, before the :db effect lands). Requires re-frame.alpha (drop-in for core: shadow-cljs :build-options {:ns-aliases {re-frame.core re-frame.alpha}}).

(ns app.cart.flows (:require [re-frame.alpha :as rf]))

(rf/reg-flow
 {:id     :cart/total
  :inputs {:items  [:cart :items]          ;; key -> app-db path, or (rf/flow<- :other-flow-id)
           :coupon [:cart :coupon]}
  :output (fn [{:keys [items coupon]}]     ;; fn of resolved inputs; whole db if no :inputs
            (apply-coupon (sum items) coupon))
  :path   [:cart :total]})                 ;; result assoc-in'd here
  • Only runs :output when resolved inputs actually changed between old and new db.
  • Layer flows with (rf/flow<- :upstream-id) as an input — dependency graph, upstream first.
  • Subscribe via a normal reg-sub on the output path, or (rf/subscribe [:flow {:id :cart/total}]).
  • Lifecycle: :live-inputs + :live? (predicate of resolved live-inputs) control when the flow is alive; on live→dead, :cleanup runs ((fn [db path] -> db), default dissocs the path; use (fn [db _] db) to keep the value).
  • Re-registering an :id replaces the flow; clear-flow removes it. From event handlers use the :reg-flow/:clear-flow effects — but only in handlers registered via re-frame.alpha (via re-frame.core they're unregistered effects and throw).
  • Caveat: the :db you return from a handler may not equal final app-db — flows mutate it afterwards.

Flows vs subs vs events

  • Event handler — when one event obviously owns the change.
  • Subscription (layer 3) — render-only derivations; result is not in app-db.
  • Flow — maintained invariants / cascading derived state that event handlers also need to read (e.g. per-field validation errors feeding a :form/submittable? state). Output lives in app-db: one source of truth, visible to handlers, runs in "re-frame time" not render time.

Other re-frame.alpha extras

  • sub/reg shorthands and map-shaped queries: (rf/sub :flow {:id :cart/total}).
  • Subscription lifecycles via :re-frame/lifecycle: :safe (default for sub — caches only when memory-safe), :reactive, :no-cache, :forever. Alpha sub/subscribe is safe to call anywhere, including event handlers, at recomputation cost outside render context.