Glossary¶
re-frame2 nouns, verbs, and concepts. One term, definition first; short code when the spelling matters; Related points at the teaching page.
The Nouns¶
adapter¶
A map of functions that binds re-frame2 core to a React-family substrate (Reagent, UIx, …). A value, not the library itself.
Install once at boot via init!:
(ns app.core
(:require [re-frame.core :as rf]
[re-frame.adapter.reagent :as reagent-adapter]))
(rf/init! reagent-adapter/adapter)
To switch substrate, change that require and pass its adapter. Events, subscriptions, and app-db stay the same.
Related: Views, Adapters. Substrate = the rendering library; adapter = the value that binds re-frame2 to it.
app-db¶
The single immutable map of application state — one per frame, the state your code owns.
You choose the shape; optionally guard it with a schema (Malli by default). You never mutate it in place: an event handler returns a new app-db, the runtime commits it atomically, and subscriptions re-derive views from that value. State enters through events and leaves through subscriptions — never the reverse.
Framework state lives separately in runtime-db inside the same frame; see the two partitions.
Related: app-db, Validate with schemas.
path¶
A vector of keys into app-db, with get-in / assoc-in semantics — [:auth :token] is the :token under :auth. Paths are the addressing form used by schemas, data-classification, flow :output-path, and the path interceptor.
Related: app-db.
coeffect¶
A fact about the world (current time, fresh UUID, localStorage value, …) the runtime supplies to an event handler as data so the handler stays pure and never reaches out itself. Handler shape: coeffects in → effect map out.
The first argument is always a coeffects map with :db (current app-db). Any other fact must be listed under :rf.cofx/requires; the runtime fills it in:
(rf/reg-event :order/place
{:rf.cofx/requires [:today]}
(fn [{:keys [db today]} _]
{:db (assoc db :order/date today)}))
Register a supplier with reg-cofx:
Declared input is what keeps events pure, testable, and replayable.
effect¶
One side effect described as data for the runtime — HTTP, navigation, delayed dispatch, localStorage write. Shape: [effect-id config], listed in the :fx vector of an effect map:
The event handler only describes the effect; the effect handler registered with reg-fx performs it. Effects are the output dual of input coeffects.
effect handler¶
The function registered with reg-fx for an effect-id. The runtime calls it once per matching entry in :fx, with that effect's config. Impure work lives here so the pure event handler stays data-only.
Built-ins cover common effects (:dispatch, :dispatch-later, :rf.http/managed, …); add your own with reg-fx.
effect map¶
What an event handler — or a machine action — returns: a description of change, not the change itself.
Two reserved keys. :db is the new app-db:
:fx is a vector of effects — each [effect-id config]:
{:db new-db
:fx [[:http {:url "/api/cart" :method :post}]
[:dispatch-later {:ms 500 :event [:cart/saved]}]]}
Only :db and :fx may appear at the top level; an unknown key fails loud. Each effect runs through an effect handler (reg-fx).
Related: Effects, Coeffects. Casual name for the :fx key: "fx".
error record¶
A failure the runtime surfaces as a structured map — fails loud, as data. Every record carries a reserved :rf.error/* category: on traced/listener records under :operation; on construction-time throws as :rf.error/id in ex-data. Branch on the category, never on human-readable :reason (prose; can change).
Error records reach always-on error listeners (Sentry, Datadog, …) and survive production (unlike the dev-only trace surface).
Related: Errors.
event¶
An inert data vector: something happened. You dispatch it; a registered event handler decides the response. The event itself does nothing.
Most events are user intent (click, drag, route change); timers, browser APIs, WebSockets, and loaders raise them too. First element is the id (namespaced keyword is the norm), then optional facts:
Prefer one payload map over positional args:
Data means log, record, and replay.
Related: Introduction.
event pipeline¶
The fixed stage sequence one dispatched event runs through, in three phases:
- update phase — compute a description of the change
- commit phase — run declared effects;
:dbwrite first, atomically - render phase — bring derivations and the screen up to date
Update and commit run per event — assemble → transform → commit → perform. Render runs once per render batch after the queue settles — derive → render.
;; update + commit (per event): assemble → transform → commit → perform
;; render (per batch): derive → render
:db is the anchor: transactional up to that write, best-effort after. The view renders from committed state, never mid-run.
One traversal is a run; the record it leaves is an epoch. Triple: pipeline (structure) / run (one trip) / epoch (record). Running the whole queue before render is a drain.
Related: Introduction. Older prose said event cascade, turn of the loop, or the loop for this traversal — prefer event pipeline / pipeline run. Machines cancellation cascade keeps its name.
run¶
One traversal of the event pipeline for a single dispatched event: update and commit for that event, then the shared render of the render batch it settles into. Pipeline = structure; run = one trip; epoch = the record. One dispatch = one run = one epoch. A drain is many runs, one render batch.
Related: Introduction.
update phase¶
First phase of the event pipeline — assemble → transform. Pure work once per event: build the world, run the handler, produce a description of change (new value + declared effects). Nothing has executed yet. A throwing handler installs nothing.
Related: Introduction.
commit phase¶
Second phase — commit → perform. Declared effects execute, once per event. The :db write always runs first: new app-db lands atomically before other effects; remaining effects then run in source order, best-effort. Pre-commit / post-commit are positions relative to that write. Only the committed value reaches the render phase.
Related: Effects, Introduction.
render phase¶
Final phase — derive → render. Brings the screen up to date after the queue settles. Once per render batch, not once per event: reads only the committed value, never a half-written app-db. A drain is never split across batches.
Related: Subscriptions, Introduction.
render batch¶
The window of pending reads and renders the render phase finishes in one go. Everything dirty since the last batch renders together. The batch closes at the host's next microtask checkpoint, or at an explicit flush in headless tests.
The boundary is the host's, not the drain's: the UI scheduler does not observe the event queue or drain edges.
- A drain cannot split across batches. Every event a drain settles renders together; no intermediate state hits the screen.
- Drains that finish before the same checkpoint may share a batch. Two back-to-back
dispatch-synccalls in one JS stack render once; drains separated by a real host yield render separately.
In ordinary app code a yield falls between drains, so "one render per drain" is a fine working model for the common case, not a hard rule.
Related: render phase, drain, Effects — run to completion.
world¶
The map of declared facts assembled for an event handler — everything outside the handler is allowed to see, as data. app-db is one entry (always present, under :db); clock, fresh id, storage reads are others declared via :rf.cofx/requires. The world is the handler's first argument — its coeffects map — built in the assemble stage.
A frame is a running world: that map plus queue, caches, and lifecycle that re-assemble it per event.
Related: Effects, Coeffects, frame.
event envelope¶
Runtime package created when an event is dispatched. Low-level; mostly router-internal.
App code dispatches an event vector (optionally with dispatch opts). Before queueing, re-frame2 wraps those into an envelope: the event vector plus target frame, origin, tracing ids, per-dispatch overrides, and the durable :rf.cofx record used for replayable coeffects such as :rf/time-ms.
Handlers get the event vector as their second argument and an assembled coeffects map as their first — not the envelope itself.
(rf/dispatch [:cart/add {:sku "BK-1"}]
{:frame :checkout
:source :ui
:trace-id :cart/add-click})
;; Rough envelope shape:
{:event [:cart/add {:sku "BK-1"}]
:frame :checkout
:source :ui
:origin :app
:trace-id :cart/add-click
:rf.cofx {:rf/time-ms 1781078400123}}
Related: Introduction, Frames, Effects, Coeffects.
event handler¶
A pure function that computes how a dispatched event should change the world. Arguments: coeffects map (including :db) and the event vector; return: effect map.
It describes changes (:db, :fx); it does not perform them. No IO, no clock, no subscription reads inside — world arrives only through declared coeffects.
Related: Introduction.
flow¶
A pure derivation re-frame2 keeps materialised at a path in app-db. Because the value lives in app-db, event handlers can read it as plain state — unlike a subscription, whose value is for views.
Declare :inputs and an :output-path in the metadata map, with the pure derive fn as the third slot. When an input changes, the runtime re-runs that fn and writes the result in step with the event pipeline:
(rf/reg-flow :cart/total
{:inputs [[:cart :items]]
:output-path [:cart :total]}
(fn [items] (reduce + (map :price items))))
Use a flow to collate many facts into one (e.g. several error flags → :any-errors?). Inputs may read framework state under :rf.db/runtime. Flows can be added and removed dynamically via effects.
Related: Flows, toggling a derivation at runtime.
frame¶
An isolated running instance of an app. A frame is a running world — declared facts (app-db among them) plus runtime machinery: runtime-db, event queue, subscription cache, lifecycle.
A frame supplies state; behaviour comes from an image. A frame isolates state, not registrations — the registry is process-global, so the same handlers, subs, views, effects, flows, and machines run in every frame against that frame's own state. Most frames use the default image (all registrations).
Most apps create one frame at boot. Multiple frames on one page power tests, stories, per-request SSR, and tools like Xray. Identity is carried, not found — each operation reads its frame from scope.
(rf/make-frame
{:id :app
:initial-events [[:app/initialise]]
:images [image1 image2]}) ;; optional selected registrations
Related: Frames.
capture-frame¶
(rf/capture-frame) returns a frame api: a small map with that frame's :dispatch / :dispatch-sync / :subscribe plus the captured :frame id. Carry it across async — grab while the frame is in scope so a later setTimeout, promise, or WebSocket callback can still target it instead of raising :rf.error/no-frame-context. (capture-frame = verb; frame api = value returned.)
Related: Frames.
frame-provider¶
React component that scopes an existing frame to a view subtree so dispatch/subscribe resolve to it. SCOPE-only — roots ensure; providers scope: {:frame existing-id} provides an already-created id via React context; creates and destroys nothing; fails loud if the frame is missing. Given :id (ENSURE key) it fails loud naming sibling frame-root. Everyday expression of frame identity is carried, not found.
Related: Frames.
frame-root¶
React component that ensures a named frame for a subtree's mounted lifetime — ENSURE sibling of frame-provider. Keyed by {:id …} (plus make-frame opts): creates if absent (at commit, in a client layout effect — discarded React renders create nothing), reuses without re-seeding if present (hot reload / StrictMode preserve app-db; never replay :initial-events), provides id to descendants. Does not destroy on unmount; ownership is explicit make-frame + destroy-frame!. Given :frame it fails loud naming frame-provider.
Related: Frames.
hiccup¶
Clojure data for UI: nested vectors — [:div.card {:on-click f} "Hi"] is a <div>. Markup as data; a view composes it; the substrate turns it into React elements (or a server string).
Related: Views.
image¶
The selected set of registrations a frame resolves behaviour against — event handlers, subscriptions, views, effect handlers, flows, machines, and the rest. An image is a value: no state, not a running app.
Most apps use the default image — all registrations already loaded. Name an image when frames need different behaviour (fake effects in tests, two examples sharing event ids, Xray sidecar).
Image supplies behaviour; frame supplies state. On start, the image resolves into a sealed registration set (generation).
(def checkout-image
(rf/image {:select-ns {:include ["app.checkout.*"]}}))
(rf/make-frame
{:id :checkout/story
:images [checkout-image]})
Related: Images.
generation¶
The sealed registration set a frame's image resolves into at construction — the concrete "which handler answers this id" table, frozen as a value. Every make-frame frame carries one (default image included). Re-calling make-frame with the same :id and a new :images vector swaps the frame onto a newly resolved generation.
Related: Images.
interceptor¶
A named wrapper around an event handler — :before / :after functions for cross-cutting work (logging, validation, tracing, undo). Each is context → context:
:beforeruns before the handler; can read/adjust coeffects:afterruns after; can read/adjust the returned effect map
Register by id with reg-interceptor (never inline). Events opt in by id:
(rf/reg-interceptor :my-app/logger
{:before (fn [ctx] ctx)
:after (fn [ctx] ctx)})
(rf/reg-event :cart/add
{:interceptors [:my-app/logger]}
(fn [{:keys [db]} [_ item]]
{:db (update db :cart/items conj item)}))
Related: Interceptors.
query vector¶
The vector passed to subscribe for a subscription: id plus optional args — [:article/page "abc"]. Id selects the sub; the whole vector keys the cache, so equal vectors share one cached value.
Related: Subscriptions.
registrar¶
The single process-wide table every reg-* writes and every lookup reads, keyed by kind + id. Holds all registrations. Every frame shares one registrar — frames isolate state, not behaviour.
registration¶
An app's behaviour is the set of registrations you provide.
One registration maps an id (usually a namespaced keyword) to a function or config the runtime looks up later.
At runtime:
- a frame supplies isolated state and execution context
- an image supplies the selected registrations
- a stream of events drives the runtime
Example: event id :cart/add → this event handler:
Core registration:
reg-event— event handlerreg-sub— subscriptionreg-fx— effect handlerreg-cofx— coeffect supplierreg-interceptor— event-handler wrapperreg-view/reg-view*— views
Frame construction is not a reg-* member — a frame is a live runtime object. make-frame creates a named frame; frame-root is the ENSURE mount recipe.
Flows:
reg-flow— flow
reg-machine/reg-machine*— machines
Routing:
reg-route— route
Schema:
reg-app-schema/reg-app-schemas— Malli schemas for app-db paths
SSR:
reg-head— SSR head producerreg-error-projector— SSR error projector
HTTP:
reg-http-interceptor— managed-HTTP middleware
runtime-db¶
Framework-owned half of a frame's state — beside app-db you own; see the two partitions.
Holds machine snapshots, current route, resource caches, mutation status, and similar. App code reads via subscriptions or accessors — never by editing :rf.db/runtime paths directly.
Related: app-db, frame. Paths: :rf.db/runtime, children :rf.runtime/*.
schema¶
A data description of a value's shape — [:map [:sku :string] [:qty :int]] — in Malli (default). Attach to an app-db path (reg-app-schema), an event, or an HTTP :decode step. Checks run at a named boundary, but whether one survives a production build depends on which boundary: reg-app-schema's app-db check and the plain event check are development assertions and elide, while an event handler carrying :rf.schema/at-boundary and a managed-HTTP :decode schema are checked in every build. Schema-as-data supports validate, coerce, and tooling round-trips.
Related: Validate with schemas.
subscription¶
A named, registered, pure, cached derivation of state — how a view reads what it needs. reg-sub; recomputes only when inputs change by =. Layers: some read app-db directly; others combine other subscriptions.
Related: Subscriptions. Casual "sub" is fine; not as a headword. Value inside an event handler? Materialise with a flow.
substrate¶
The React-family rendering layer — Reagent, UIx, or reagent-slim. Wire re-frame2 to it with an adapter. Core is substrate-agnostic: events, subscriptions, and app-db stay the same; only rendering differs.
Related: Use UIx or slim.
view¶
A pure render function from subscription values to hiccup. Reads derived state; dispatches events on interaction; no business logic. The substrate turns hiccup into React elements.
Related: Views. Use "component" only in React-analogy callouts.
The Verbs¶
The six pipeline stages — assemble → transform (update phase, per event), commit → perform (commit phase, per event), derive → render (render phase, per render batch) — in pipeline order.
assemble¶
First stage: gather the world the event handler will read — app-db (:db) plus every fact listed in :rf.cofx/requires — into one coeffects map before the handler runs.
Related: Effects, Coeffects, world.
transform¶
Second stage: run the pure event handler — assembled world and event in, effect map out. Transforms world into a description ({:db … :fx …}); performs none of it. Later stages after commit execute that description.
Related: Introduction, event handler.
commit¶
The single, deferred, all-or-nothing write of the new app-db — first effect of the commit phase, and its anchor. Special only in that it always runs first; it is the one point the committed value crosses to the render phase. Before it (assemble, transform): transactional — the :db the handler returns is staged and lands once, after flows run; a throwing handler or flow installs nothing. After it (perform): best-effort. No observer sees a half-written app-db.
Related: Introduction.
perform¶
Fourth stage; rest of the commit phase: run :fx rows from transform in source order after commit. Only place the system touches the outside world (HTTP, navigation, follow-up dispatch), via each id's effect handler. Past the :db write: best-effort — a throwing effect does not un-commit state.
Related: Effects, Coeffects, effect.
derive¶
Fifth stage; first of the render phase: recompute subscriptions (and the derivation graph) that watch changed parts of committed app-db. Values equal by = to last time prune everything downstream. Once per render batch, against settled state. Public verb: subscribe; derive names the stage.
Related: Subscriptions.
render¶
Sixth stage: views that deref a changed subscription re-run, produce fresh hiccup; the substrate patches the DOM that moved. Once per render batch from settled state — a drain is never split across batches — so the screen does not flash intermediate values.
Related: Views.
dispatch¶
Wraps the event in an event envelope and returns immediately; the event handler runs later in the pipeline run that event starts. Sibling dispatch-sync runs the pipeline now (tests, boot).
Related: event, event envelope, event pipeline.
dispatch-sync¶
Like dispatch, but runs the event and normally drains the whole queue to completion before returning. A drain-depth halt or successful exact-incarnation destruction claim is terminal. At a destroy claim, an authored callback already on the stack may return and entered interceptor :after callbacks may unwind, but the returned framework tail is inert and later ordinary events do not begin. Use at boot, in tests, and at the REPL — never from inside a running handler (:rf.error/dispatch-sync-in-handler).
Related: Introduction.
drain / run-to-completion¶
The runtime normally drains the whole event queue to a fixed point — update and commit of every queued event — before the render phase. Update and commit run per event; everything the drain settles lands in one render batch; the UI updates once from settled state. A drain-depth halt or successful exact-incarnation destruction claim is terminal. At a destroy claim, authored callbacks already on the stack may return and entered interceptor :after callbacks may unwind; the returned framework tail is inert; later ordinary events do not begin and no render phase follows the interrupted event. A drain is normally many pipeline runs sharing one render phase.
;; every queued event's update + commit, THEN — at the host's next
;; checkpoint, once — subs recompute and views render
Related: Effects — run to completion (idea + demo);
Run to completion (detail) (drain-depth, dispatch-sync).
Hyphenate run-to-completion consistently.
elide¶
Compile dev-only code out of production via one flag (goog.DEBUG or -Dre-frame.debug). Removes the dev trace surface, the epoch buffer, and the ordinary registration diagnostics among the schema checks. What elides is settled by what the check is for, not by who declared the schema it reads: a check the framework relies on to keep a promise of its own — :rf.schema/at-boundary, a recordable coeffect's :schema, a declared route's shape — holds in every build, and those three all validate against a schema the programmer wrote. Always-on :errors and :events streams survive.
;; goog.DEBUG=false removes the dev trace surface and the schema
;; checks you declared — not the framework's own boundary checks
Related: Observability, Configure dev and production builds, schema. Name DCE once, then use elide.
init!¶
One-time boot call that installs a substrate adapter — (rf/init! reagent-adapter/adapter). Idempotent. Does not create a default frame (identity is carried, not found); you establish the root frame explicitly.
Related: Adapters.
project (egress)¶
Run a value through redaction before it leaves the app via project-egress. Direct reads are not auto-projected.
Related: Keep secrets out of traces.
register¶
Name handlers and machinery at boot with registration forms — reg-event, reg-sub, and so on.
Related: Introduction. There is one reg-event: reg-event-db / -fx / -ctx are gone.
subscribe / derive¶
Read derived state by name through a subscription. @(subscribe …) reads the current value and subscribes so the view re-renders on change.
Related: Subscriptions.
The Concepts¶
Effects are data¶
An event handler returns a description of side effects — an effect map of data — and the runtime performs them. Pure handler + data effects enable replay, test, and trace.
Fail loud, not silent¶
A recognised input that cannot be honoured raises a structured error record (:rf.error/*), never a nil or no-op. Fail-loud = raise instead of swallow. Fail-closed = deny by default at a boundary. Keep them distinct.
Related: Errors.
Frame identity is carried, not found¶
An operation reads its frame from scope (provider / running handler / captured handle). The runtime never invents one. A rootless call is :rf.error/no-frame-context.
Related: Frames.
The four homes (where state lives)¶
Subscription → flow → resource → machine: pick the cheapest that fits. Full router: Where state lives.
Related: Where state lives.
The two partitions¶
A frame holds app-db (yours) and runtime-db (framework), addressed by :rf.db/app and :rf.db/runtime; subsystems under :rf.runtime/*.
Related: app-db.
The uniform reply¶
Every managed async surface (HTTP, resources, mutations, route loaders, machine async) completes by dispatching an event with one canonical reply map — never an awaited value. Discriminator is closed :status: :ok (value at :value), :error (failure at :error), :cancelled, or :stale. Same envelope on every surface, HTTP included. Distinct from the resource read sub's :status lifecycle (:idle / :loading / :fetching / :loaded / :error).
Related: Managed HTTP.
The derivation graph¶
Directed graph of pure derivations rooted at app-db, views at the leaves. Subscriptions, flows, resource reads, route facts, and machine selectors are nodes. The runtime recomputes only along edges whose value changed by =; unchanged input prunes everything downstream.
Related: Subscriptions.
Data classification¶
Marking an app-db path (or payload slot) :sensitive or :large so the runtime swaps in a redaction/size sentinel wherever that value would cross an egress boundary (trace, Xray, SSR payload, off-box log). On-box rendering still sees the real value. Hygiene at the boundary (see project (egress)), not security.
Related: Keep secrets out of traces.
Recordable vs ambient coeffects¶
Two grades of coeffect. Recordable (clock, fresh id) is captured onto the event envelope before the handler runs so durable results replay identically. Ambient is read live and not recorded — fine for a display hint, never for a durable write.
Observability¶
Tools read the pipeline through this surface. Trace stream and epoch history are dev-only (see elide); always-on error and event streams survive production. See Observability.
trace stream¶
Live in-process feed of trace events at every pipeline stage — dispatch, handler, sub recompute, effect. Tools (Xray, Story, pair MCP) are readers of it. Dev-only — elided from production.
trace event¶
One immutable record on the trace stream: :operation, :op-type, timestamp, tags (including the id that correlates a whole pipeline run). Filter by :op-type. Always-on :errors / :events records are the production-surviving subset.
listener¶
Callback registered with register-listener! on a named stream — :trace, :epoch, :events, or :errors — fired on each matching emit. One registration is a tooling integration. Listeners see data in the clear — project before sending off-box.
epoch¶
The record one pipeline run leaves — trigger event, before/after app-db, run's trace events. Unit of time-travel: Xray rewinds, replays, and inspects one epoch at a time. Triple: pipeline / run / epoch.
Dev-only — elided from production.
Related: Observability.
time-travel¶
Restore a frame to the state it held at an earlier epoch — both partitions, one atomic write, no handlers re-run. Each epoch holds real before/after immutable values. Powers Xray scrubbing and undo.
Xray¶
Dev inspector: in-app panel over the trace stream and per-frame epoch history. Debug the pipeline, not the DOM.
Related: the Xray docs.
Story¶
View workbench: render a view's loading, empty, error, and happy states as named variants, each in its own frame; promote good examples into tests. Reads the same trace stream as other tools.
Related: the Story tab, Observability.