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
:outputwhen 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-subon 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,:cleanupruns ((fn [db path] -> db), default dissocs the path; use(fn [db _] db)to keep the value). - Re-registering an
:idreplaces the flow;clear-flowremoves it. From event handlers use the:reg-flow/:clear-floweffects — but only in handlers registered viare-frame.alpha(viare-frame.corethey're unregistered effects and throw). - Caveat: the
:dbyou 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/regshorthands and map-shaped queries:(rf/sub :flow {:id :cart/total}).- Subscription lifecycles via
:re-frame/lifecycle::safe(default forsub— caches only when memory-safe),:reactive,:no-cache,:forever. Alphasub/subscribeis safe to call anywhere, including event handlers, at recomputation cost outside render context.