re-frame.adapter.reagent¶
Use this namespace to run a re-frame2 app on Reagent. It provides the adapter you pass to rf/init! at boot, and the client-root, render! and unmount! functions your entry namespace mounts the app through.
Pick it when your views are hiccup; the Core guide uses it. If your components are React function components written with UIx hooks, use re-frame.adapter.uix instead. It ships in day8/re-frame2-reagent; Artefact and floor has the coordinate and the React and Reagent versions it needs.
(rf/reg-event :counter/inc
(fn [{:keys [db]} _]
{:db (update db :counter/value (fnil inc 0))}))
(rf/reg-sub :counter/value
(fn [db _] (:counter/value db 0)))
(rf/reg-view counter []
[:button {:on-click #(dispatch [:counter/inc])}
"Clicked " @(subscribe [:counter/value]) " times"])
(defonce app-root (reagent-adapter/client-root))
(defn ^:dev/after-load mount! []
(when-let [el (and (exists? js/document)
(js/document.getElementById "app"))]
(reagent-adapter/render! app-root
[rf/frame-root {:id :rf/default}
[counter]]
el)))
(defn run []
(rf/init! reagent-adapter/adapter)
(mount!))
Everything else a Reagent app calls is on re-frame.core: reg-view, the frame-root and frame-provider components, capture-frame, with-frame, and the lifecycle functions init!, destroy-adapter! and current-adapter. There are no hooks here: a view registered with reg-view gets dispatch and subscribe injected instead. A plain Reagent function rendered as a component reads the surrounding frame-root or frame-provider while it renders, as a registered view does. A callback that runs after the render, such as an :on-click or a timer, has no frame to read, so an ambient rf/dispatch there raises :rf.error/no-frame-context; capture the frame during the render with rf/capture-frame (a reg-view injects ops captured this way), or name the frame with {:frame …}. The dependency is one-way: this namespace requires re-frame.core, and core never requires it. Boot and mount an app walks through the entry namespace.
Adapter spec¶
adapter¶
- Kind: var (map)
- Signature:
- Description: The adapter map you pass to
rf/init!to install Reagent as the substrate (stockreagent.core/reagent.dom.client).- Installation is explicit: there is no default adapter and no keyword form, so require this namespace and pass
adapterat the call site. - Once it is installed,
(rf/current-adapter)returns this map, and its presence is how you ask whether an adapter is installed.(:kind (rf/current-adapter))reads:rf.adapter/reagent. - Frames are scoped with core's
frame-providerandframe-root, whose children are trailing hiccup:[rf/frame-provider {:frame …} & children].
- Installation is explicit: there is no default adapter and no keyword form, so require this namespace and pass
- Example:
Artefact and floor¶
The adapter ships in day8/re-frame2-reagent, which carries stock Reagent (reagent.core, reagent.dom.client, reagent.dom.server). It needs React 19 and runs on Reagent 2.x; there is no React 17/18 or Reagent 1.x path.
A view rendered in a pass that React discards before committing keeps its subscriptions for the life of the page. Such passes include a Suspense boundary suspending on first mount, an error boundary catching on mount, and a hidden Activity that is never shown. Each change to one of those subscriptions force-updates the never-mounted instance, and React's development build warns about it. Stock Reagent gives the adapter no commit signal to release them. UIx releases them within one macrotask, so prefer it where those patterns matter.
The client root¶
A browser app needs one React root for the life of the page: created once, updated on every hot reload, released on teardown. client-root, render! and unmount! manage that root, so your entry namespace never creates one or touches reagent.dom.client. Allocate the handle under a defonce and call render! from the ^:dev/after-load hook, as in the example at the top of this page, with run as the build's :init-fn. On a hot reload shadow-cljs calls mount! again: the defonce keeps the handle, render! updates the same root, and frame-root reuses the live frame without re-running :initial-events, so app-db survives the reload.
The raw React root is never exposed. rf/destroy-adapter! also releases it, exactly once.
client-root¶
- Kind: function
- Signature:
- Description: Returns a new, inert client-root handle. It does no DOM work, so it is safe at namespace load under a
defonce, in tests and on Node; the firstrender!through the handle creates (or hydrates) the React root.- The handle is opaque: pass it to
render!andunmount!and nothing else.
- The handle is opaque: pass it to
- Example:
render!¶
- Kind: function
- Signature:
- Description: Renders
render-tree(hiccup) into the DOM elementmount-pointthroughhandle: the first call creates the React root, and every later call updates it. Returns nil.- With
{:hydrate? true}the first call hydrates the server-rendered markup already insidemount-pointinstead (seere-frame.ssr). Later calls never create a second root or hydrate a second time. - A hydrating first call also reads
:on-recoverable-error, which React calls with each error it recovers from, and:identifier-prefix, which React'suseIdneeds to match the server's and must be the prefix the server rendered under. In every build, a hydration mismatch React recovers from is reported first as an always-on:rf.ssr/hydration-mismatcherror record that reaches your error sinks, and in a development build also as the trace of the same id; then your callback runs, or React's default report. - Because later calls update the same root, one call serves as both the boot path and the
^:dev/after-loadhook.mount-pointis read on the first call only. - After
unmount!, or afterrf/destroy-adapter!has released the root, the nextrender!mounts afresh. - Call
rf/init!before the firstrender!.render!does not check for an adapter, but the first frame or subscription the tree creates raises:rf.error/no-adapter-installed, or:rf.error/adapter-disposedafterrf/destroy-adapter!. Install an adapter again before rendering afresh.
- With
- Example:
(reagent-adapter/render! app-root [rf/frame-root {:id :rf/default} [counter]] el) ;; Later calls update the same root and reuse its frame. ;; Alternative boot for an SSR page: hydrate on this handle's FIRST render. (defonce hydrated-root (reagent-adapter/client-root)) (reagent-adapter/render! hydrated-root [rf/frame-provider {:frame :rf/default} [counter]] el {:hydrate? true}) ;; Hydration has already installed :rf/default; later renders update this root.
unmount!¶
- Kind: function
- Signature:
- Description: Unmounts the React root
handleholds and returns the handle to inert, so a laterrender!mounts afresh. Returns nil.- Unmounting releases the view tree; it does not destroy the frames used by that tree. A
frame-rootreuses its live frame on remount. If the app owns a frame that should end here, callrf/destroy-frame!separately. - Idempotent: a second call, or a call after
rf/destroy-adapter!has released the root, does nothing.
- Unmounting releases the view tree; it does not destroy the frames used by that tree. A
- Example:
Server-side rendering¶
set-hiccup-emitter!¶
- Kind: function
- Signature:
- Description: Installs the function the adapter's
render-to-stringuses to turn a render tree into HTML. Requiringre-frame.ssrinstalls it for you, so you rarely call this directly.ftakes the render tree and an opts map, and returns an HTML string.- Last call wins; pass
nilto reset. - With no emitter installed,
render-to-stringraises:rf.error/no-hiccup-emitter-bound. rf/destroy-adapter!clears the installed emitter. The nextrf/init!re-installsre-frame.ssr's emitter when that namespace is loaded, and otherwise leaves the slot empty. An emitter you install beforerf/init!is kept, so install a custom emitter again after re-initialising.- The UIx adapter has the same function.
- Example:
See also¶
re-frame.adapter.uix— the hooks-first React adapter, with the same client-root functions.re-frame.ssr— server-side rendering; installsset-hiccup-emitter!for you.- Use UIx — choosing a substrate, and moving an app's views to UIx.
- Views — why the substrate only shows up in the view body.
- Adapter and substrate in the glossary.