Glossary¶
re-frame2 terms, one entry each: the definition first, a short example where the spelling matters, and a link to the page that teaches it.
The Nouns¶
adapter¶
The value that connects re-frame2 to a view layer (the substrate). It
is a map of functions, not the library itself. Fresco's is
re-frame.fresco.substrate/adapter; Reagent, UIx and reagent-slim each ship one.
Install it once at boot with init!:
(ns app.core
(:require [re-frame.core :as rf]
[re-frame.adapter.reagent :as reagent-adapter]))
(rf/init! reagent-adapter/adapter)
To switch view layer, require a different adapter. Events, subscriptions and app-db stay the same.
Related: Views, Fresco installation, Reagent adapter.
app-db¶
The single immutable map of application state, one per frame. You choose its shape. An event handler returns a new app-db, the runtime commits it, and subscriptions derive what views show from it.
Framework state lives beside it in runtime-db; see the two partitions.
Related: app-db, Validate with schemas.
path¶
A vector of keys into app-db, read and written like get-in and
assoc-in: [:todos 1 :done?]. Schemas, data classification,
a flow's :output-path and the path interceptor all address app-db this way.
Related: app-db.
coeffect¶
A fact about the world (the time, a fresh id, a stored value) that the runtime hands
to an event handler as data, so the handler never reaches out
itself. The handler's first argument is the world map: :db is always
there, and any other fact is listed under :rf.cofx/requires. The clock,
:rf/time-ms, is built in:
(rf/reg-event :todo/add
{:rf.cofx/requires [:rf/time-ms]}
(fn [{:keys [db rf/time-ms]} [_ title]]
(let [id (inc (apply max 0 (keys (:todos db))))]
{:db (assoc-in db [:todos id]
{:id id :title title :done? false :created-at time-ms})})))
Register other suppliers with reg-cofx. A fact
that feeds a durable write, as here, must be recordable.
Related: Coeffects.
effect¶
One side effect, described as data: [effect-id args], listed in the :fx vector of
an effect map. The event handler only describes it; the
effect handler registered for that id performs it.
Related: Effects.
effect handler¶
The function registered with reg-fx for an effect id. The runtime calls it once per
matching :fx entry, with a small context map and that entry's args. Impure work
lives here, so event handlers stay pure.
Built-ins include :dispatch, :dispatch-later and :rf.http/managed.
Related: Effects.
effect map¶
What an event handler (or a
machine action) returns: a description of the
change. :db is the new app-db; :fx is a vector of
effects:
{:db new-db
:fx [[:rf.http/managed {:request {:method :post :url "/api/todos"}
:on-success [:todo/saved]
:on-failure [:todo/save-failed]}]
[:todo.storage/save todos]]}
The top level is closed. Besides :db and :fx it accepts :rf.db/runtime and the
classification keys :sensitive, :large, :clear-sensitive and :clear-large.
Any other key fails loud and nothing commits.
Related: Effects.
error record¶
The structured map the runtime produces when something fails. Its category, an
:rf.error/* keyword, is under :operation on a traced record, under :error on the
record an :errors sink receives, and under :rf.error/id in ex-data when
the framework throws. Branch on the category, never on the human-readable :reason.
{:op-type :error
:operation :rf.error/no-such-fx
:recovery :no-recovery
:tags {:rf.fx/id :todo.storage/sav :failing-id :todo.storage/sav ,,,}} ;; a typo'd fx id
The full record is on the dev-only trace stream. A smaller,
redacted record reaches the frame's :errors sink and survives production.
Related: Errors.
event¶
A data vector saying that something happened. You dispatch it, and the registered event handler decides what changes. The first element is the id; further elements are facts, a single value or a payload map when there are several:
Because an event is data, it can be logged, recorded and replayed.
Related: Events.
event pipeline¶
The fixed stages one dispatched event goes through, in three phases:
- update phase: run the handler to describe the change
- commit phase: apply it,
:dbfirst, then the other effects - render phase: recompute subscriptions and re-render views
Update and commit run once per event (assemble → transform → commit → perform). Render runs once per render batch after the queue settles (derive → render).
One pass through the pipeline is a run; the record it leaves is an epoch.
Related: Introduction.
run¶
One pass through the event pipeline for one dispatched event. One dispatch is one run is one epoch. A drain is many runs sharing one render.
Related: Introduction.
update phase¶
The first phase of the event pipeline: assemble the handler's inputs, then transform them by running the handler. It is pure and executes nothing, so a throwing handler installs nothing.
Related: Introduction.
commit phase¶
The second phase: commit the new app-db, then perform the other
effects in order. The :db write always comes first and is atomic; the effects after
it are best-effort.
Related: Effects, Introduction.
render phase¶
The last phase: derive changed subscriptions, then render the views that read them. It runs once per render batch, not once per event, and only ever sees committed state.
Related: Subscriptions, Introduction.
render batch¶
The set of pending recomputes and renders the render phase handles
in one go. It closes at the host's next microtask checkpoint, or at an explicit flush
in headless tests. A drain is never split across
batches, and two drains that finish before the same checkpoint (two back-to-back
dispatch-sync calls, say) may share one. In ordinary app code, "one render per
drain" is a good working model.
Related: Effects: run to completion.
world¶
The map of facts an event handler receives as its first argument:
app-db under :db, plus every coeffect it declared. It is
built in the assemble stage.
Related: Coeffects.
event envelope¶
The runtime's internal wrapper around a dispatched event: the event vector
plus its target frame, origin, tracing ids, per-dispatch options, and the
recorded values of replayable coeffects such as :rf/time-ms. Handlers never see it;
they get the event vector and the world map. You meet it only in tools and
low-level code.
Related: Frames.
event handler¶
The pure function registered with reg-event. It takes the world map
(including :db) and the event vector, and returns an
effect map. It describes changes; it does not perform them.
Related: Events.
flow¶
A derived value that re-frame2 keeps written at a path in app-db, so event handlers can read it as plain state. (A subscription's value is for views only.) You declare the input paths, the output path, and a pure function:
(rf/reg-flow :todo/remaining-count
{:inputs [[:todos]]
:output-path [:remaining-count]
:frame :app}
(fn [todos] (count (remove :done? (vals todos)))))
A flow belongs to one frame. Flows can also be added and removed at run time through effects.
Related: Flows.
frame¶
One running instance of an app: its app-db, an event queue, and caches.
Every frame uses the same registered handlers (unless an image narrows
them), each against its own state. Most apps have one frame, :app; tests, stories,
SSR requests and tools like Xray use more.
Related: Frames.
capture-frame¶
(rf/capture-frame) returns a frame api: a map with the current frame's
:dispatch, :dispatch-sync and :subscribe, plus its :frame id. Call it while the frame is in
scope and use the result in a later setTimeout, promise or WebSocket callback, which
would otherwise raise :rf.error/no-frame-context.
Related: Frames.
frame-provider¶
A component that makes an existing frame current for a view subtree, so
dispatch and subscribe inside it reach that frame. It creates and destroys
nothing, and fails loud if the frame does not exist. Use
frame-root to create one.
Related: Frames.
frame-root¶
A component that ensures a named frame exists and makes it current for its
subtree. On first mount it creates the frame and runs :initial-events; on later
mounts it reuses the live frame without re-seeding. It does not destroy the frame on
unmount; call destroy-frame! for that.
Related: Frames.
hiccup¶
UI written as Clojure data: [:div.card "Hi"] is a <div class="card">. A
view returns it, and the view layer turns it into React elements (or, on the
server, an HTML string).
Related: Hiccup.
image¶
The set of registrations a frame uses. Most frames use the default
image, every registration that is loaded. Name an image with rf/image when frames
need different behaviour: fake effects in a test, two examples that share event ids,
or a tool running beside the app. The image supplies behaviour; the frame supplies
state.
(def todos-image
(rf/image {:select-ns {:include ["app.todos"]}}))
(rf/make-frame {:id :app :images [todos-image]})
Related: Images.
generation¶
The frozen table of registrations a frame's image resolves into: for each
kind and id, which handler answers. Every frame has one. Calling make-frame again
with the same :id and new :images swaps the frame onto a new generation.
Related: Images.
interceptor¶
A registered wrapper around event handlers for cross-cutting work
such as logging or undo. :before runs before the handler and can adjust its inputs;
:after runs after and can adjust the effect map. Events opt in by
id:
(rf/reg-interceptor :app/logger
{:before (fn [ctx] ctx)
:after (fn [ctx] ctx)})
(rf/reg-event :todo/add
{:interceptors [:app/logger]}
(fn [{:keys [db]} [_ title]] {:db db}))
Related: Interceptors.
query vector¶
The vector passed to subscribe: a sub id plus optional arguments. The whole vector
is the cache key, so equal vectors share one cached value.
Related: Subscriptions.
registrar¶
The process-wide table that reg-event, reg-sub, reg-fx and the other shared
reg-* calls write to, keyed by kind and id. A
frame looks its handlers up in this table, through its image.
Related: Images.
registration¶
One entry in the registrar: an id mapped to a function or config that the runtime looks up later. An app's behaviour is the set of registrations it makes.
| Call | Registers |
|---|---|
reg-event |
an event handler |
reg-sub |
a subscription |
reg-fx |
an effect handler |
reg-cofx |
a coeffect supplier |
reg-interceptor |
an interceptor |
reg-view / reg-view* |
a view |
reg-flow, reg-app-schema and reg-http-interceptor are different: each
registers into one frame (the one in scope, or :frame in its metadata), not the
registrar. Other artefacts add their own registrations: reg-machine
(machines), reg-route (routing), reg-resource and
reg-mutation (resources), and reg-head and
reg-error-projector (SSR). A frame is not a registration; you
create one with make-frame or frame-root.
runtime-db¶
The framework's half of a frame's state, beside the app-db you own. It holds machine snapshots, the current route, resource caches and similar. Read it through subscriptions and accessors; don't edit its paths directly.
Related: the two partitions.
schema¶
A data description of a value's shape, in Malli by default:
[:map [:id :int] [:title :string] [:done? :boolean]]. You attach one to an app-db
path (reg-app-schema), an event, or an HTTP :decode step. App-db and ordinary
event checks run only in dev; a :boundary? true event schema, a recordable
coeffect's schema, a declared route's shape, a managed-HTTP :decode schema and the
reserved :rf.server/* effects' arguments are checked in every build.
Related: Validate with schemas, Errors.
subscription¶
A registered, cached query that a view reads. It either reads
app-db directly or derives from other subscriptions, and recomputes only
when its inputs change by =.
(rf/reg-sub :todo/remaining-count {:inputs [[:todo/all]]}
(fn [[todos] _] (count (remove :done? todos))))
Casual "sub" is fine. To use a derived value inside an event handler, use a flow.
Related: Subscriptions.
substrate¶
The view layer that renders to React: Fresco (re-frame2's own), Reagent, UIx or reagent-slim. An adapter connects re-frame2 to it. Events, subscriptions and app-db are the same on every substrate; only how views are written differs.
Related: Fresco, Use UIx or slim.
view¶
A function from subscription values to hiccup. It reads
state and dispatches events; it holds no business logic. It re-renders
when a subscription it read changes. With the Reagent-family adapters a view is a
reg-view, which provides a frame-bound subscribe and dispatch:
In Fresco a view is an h/defview that reads with h/sub:
;; (:require [re-frame.fresco :as h])
(h/defview todo-footer [_]
[:p (h/sub [:todo/remaining-count]) " left to do"])
The Verbs¶
The six pipeline stages, in order: assemble and transform (update phase), commit and perform (commit phase), derive and render (render phase).
assemble¶
Build the world the event handler will receive:
app-db under :db plus every fact listed in :rf.cofx/requires.
Related: Coeffects.
transform¶
Run the pure event handler: world and event in, effect map out. Nothing is executed yet.
Related: Events.
commit¶
Write the new app-db, once and atomically. It is the first step of the commit phase. Everything before it can be abandoned: a throwing handler or flow installs nothing. Everything after it is best-effort.
Related: Introduction.
perform¶
Run the :fx entries, in order, after the commit, each through its
effect handler. This is the only stage that touches the outside
world. A throwing effect does not undo the commit.
Related: Effects.
derive¶
Recompute the subscriptions whose inputs changed in the committed
app-db. A result equal by = to the previous one stops recomputation downstream. The
public call is subscribe; "derive" names the stage.
Related: Subscriptions.
render¶
Re-run the views that read a changed subscription, producing new hiccup; React then patches the DOM. It happens once per render batch, so the screen never shows intermediate values.
Related: Views.
dispatch¶
Put an event on a frame's queue. dispatch returns immediately;
the handler runs shortly after.
Inside a reg-view, use the injected dispatch, which already knows its frame.
Related: Events.
dispatch-sync¶
Like dispatch, but processes the event and drains the whole queue
before returning. Use it at boot, in tests and at the REPL, never from inside a
running handler (:rf.error/dispatch-sync-in-handler).
Related: Run to completion.
drain / run-to-completion¶
Processing every queued event (update and commit for each) before the render phase runs once for all of them, so the UI updates once from settled state. A drain stops early if it hits the re-entrancy depth limit or its frame is destroyed.
Related: Effects: run to completion, Run to completion (detail).
elide¶
Compile dev-only code out of a production build, controlled by one flag (goog.DEBUG
in ClojureScript, -Dre-frame.debug on the JVM). It removes the
trace stream, the epoch history and the schema checks you
declared. The always-on error and handled-event records survive, and so do the
framework's own boundary checks (a :boundary? true event schema, a recordable
coeffect's schema, a declared route's shape, a managed-HTTP :decode schema and the
reserved :rf.server/* effects' arguments).
Related: Observability, Configure dev and production builds.
init!¶
The boot call that installs an adapter: (rf/init! reagent-adapter/adapter).
Calling it again with the same adapter does nothing; a different adapter throws
:rf.error/adapter-already-installed. It does not create a frame.
Related: Boot and mount an app.
project (egress)¶
Redact a value under a frame's data classification before it
leaves the app, with project-egress. Reads inside the app are never projected.
Related: Keep secrets out of traces.
register¶
Add a registration with a reg-* call.
(rf/reg-event :todo/clear-done
(fn [{:keys [db]} _]
{:db (update db :todos #(into {} (remove (comp :done? val)) %))}))
There is one reg-event; v1's reg-event-db, reg-event-fx and reg-event-ctx
do not exist.
Related: Events.
subscribe / derive¶
Read a subscription by its query vector. Dereferencing the result in a view gives the current value and re-renders the view when it changes.
Related: Subscriptions.
The Concepts¶
Effects are data¶
An event handler returns a description of its side effects, and the runtime performs them. Pure handlers with data effects can be replayed, tested and traced.
Related: Effects.
Fail loud, not silent¶
When the runtime cannot do what was asked (an unregistered id, a missing coeffect, an
unknown effect), it emits a structured error record naming the
problem, even when it carries on (a missing fx is dropped, a missing sub reads nil).
Nothing fails silently. Fail-loud (report instead of swallow) is different from
fail-closed (deny by default at a boundary).
Related: Errors.
Frame identity is carried, not found¶
Every operation gets its frame from the surrounding scope: a frame-root or
provider, the running handler, or a captured frame. The runtime never falls back to a
default, so a call with no frame in scope raises :rf.error/no-frame-context.
Related: Frames.
The four homes (where state lives)¶
The places derived and async state can live, cheapest first: subscription, flow, resource, machine. Pick the cheapest that fits.
Related: Where should this value live?
The two partitions¶
A frame's state has two parts: app-db, which you own, and
runtime-db, which the framework owns. Their paths are :rf.db/app
and :rf.db/runtime, with subsystems under :rf.runtime/*.
Related: app-db.
The uniform reply¶
Every managed async operation (HTTP, resources, mutations, route loaders, machine
async) finishes by dispatching your event with one reply map, keyed by
:status: :ok (value at :value), :partial (both :value and :error),
:error (failure at :error) or :cancelled. A fifth status, :stale, marks a
reply whose request went obsolete; it is traced and never dispatched. This is different from a
resource read's :status (:idle, :loading, :fetching, :loaded, :error).
Related: Managed HTTP.
The derivation graph¶
The graph of pure derivations that starts at app-db and ends at
views. Subscriptions, flows, resource reads, route
facts and machine selectors are its nodes. The runtime recomputes only along edges
whose value changed by =.
Related: Subscriptions, One graph.
Data classification¶
Marking an app-db path :sensitive or :large, so the runtime replaces
its value with a redaction or size marker wherever it leaves the app (traces,
Xray, SSR payloads, off-box logs). Rendering in the app still sees the real
value. It is hygiene at the boundary, not a security control.
Related: Keep secrets out of traces.
Recordable vs ambient coeffects¶
A recordable coeffect (the clock, a fresh id) is captured when the event is dispatched, so a replay sees the same value. An ambient one is read live and not recorded: fine for a display hint, never for a durable write.
Related: Coeffects.
Observability¶
The trace stream and epoch history are dev-only (see elide). In
production, error and handled-event records reach :observability sinks.
trace stream¶
The in-process feed of trace events the runtime emits as each event runs: dispatch, handler, subscription recompute, effect, render. Xray, Story and the pair MCP read it, and so can your own listener. Dev-only.
Related: Observability.
trace event¶
One map on the trace stream, with :op-type (the family),
:operation (what happened), :time and :tags. Every trace event from one run has
the same :rf.trace/dispatch-id.
Related: Observability.
listener¶
A callback registered with register-listener! on the :trace or :epoch stream.
Both streams are dev-only; use a sink in production. Trace payloads are
classified at capture, but epoch listeners receive raw state snapshots. Always
project a record before sending it off-box.
Related: Observability.
sink¶
A function registered with register-observability-sink! and named in a frame's
:observability config (or once for the process with configure!). The :errors
stream delivers error records; :handled-events delivers one
record per handled event. Sinks work in production, and every record arrives already
redacted under the frame's data classification.
(rf/configure! {:observability {:errors [{:sink :app/sentry}]}})
(rf/register-observability-sink! :app/sentry (fn [record] (send-to-sentry! record)))
Related: Report errors in production.
epoch¶
The record one run leaves: the event, app-db before and after, and the run's trace events. Xray steps through and rewinds epochs. Dev-only.
Related: Observability.
time-travel¶
Restoring a frame to the state it held after an earlier epoch,
with restore-epoch!. Both partitions are restored in one write, and no handlers
re-run.
Related: Observability.
Xray¶
The dev inspector: an in-app panel over the trace stream and each frame's epoch history. It shows what each event did, app-db diffs, and supports time travel.
Related: the Xray docs.
Story¶
A view workbench: it renders a view's loading, empty, error and happy states as named variants, each in its own frame, and turns good examples into tests.
Related: the Story docs.