View lifecycle vocabulary shared across page handlers (and, later, reactive sub-regions and async workers).
Hyper page handlers climb a Reagent-style form ladder, chosen by the ownership question "what does this view own?":
form-1 — owns nothing. A pure (fn [req] -> hiccup). This is the
default and needs nothing from this namespace.
form-2 — owns only framework-managed subscriptions (auto-cleaned). The handler returns a setup closure that runs once per mount and returns the pure render fn:
(fn [req] ;; setup — runs once, effects allowed (h/watch! some-source) (let [data* (h/tab-cursor :data)] (fn [req] ;; render — runs every render, must be pure [:div @data*])))
form-3 — owns an external resource that needs explicit teardown.
The handler returns a View (via view) whose :mount allocates the
resource and returns it; the framework threads that value into
:render and :unmount:
(h/view {:mount (fn [] (open-conn! ...)) :render (fn [conn req] [:div (query conn)]) :unmount (fn [conn] (close! conn))})
There is deliberately no per-view mutable ctx slot: the server is
always in declarative-rerender mode, so a resource opened at mount,
read during renders, and closed at unmount is simply a value threaded
immutably through the lifecycle.
View lifecycle vocabulary shared across page handlers (and, later,
reactive sub-regions and async workers).
Hyper page handlers climb a Reagent-style form ladder, chosen by the
ownership question "what does this view own?":
- **form-1** — owns nothing. A pure `(fn [req] -> hiccup)`. This is the
default and needs nothing from this namespace.
- **form-2** — owns only framework-managed subscriptions (auto-cleaned).
The handler returns a *setup* closure that runs once per mount and
returns the pure render fn:
(fn [req] ;; setup — runs once, effects allowed
(h/watch! some-source)
(let [data* (h/tab-cursor :data)]
(fn [req] ;; render — runs every render, must be pure
[:div @data*])))
- **form-3** — owns an external resource that needs explicit teardown.
The handler returns a `View` (via `view`) whose `:mount` allocates the
resource and *returns* it; the framework threads that value into
`:render` and `:unmount`:
(h/view
{:mount (fn [] (open-conn! ...))
:render (fn [conn req] [:div (query conn)])
:unmount (fn [conn] (close! conn))})
There is deliberately no per-view mutable `ctx` slot: the server is
always in declarative-rerender mode, so a resource opened at mount,
read during renders, and closed at unmount is simply a value threaded
immutably through the lifecycle.(render-page app-state* tab-id handler req render-error-fn wrap-mw)Resolve and render a page handler through the form-1/2/3 ladder, managing the per-tab page-view lifecycle and the render purity guard.
Returns the raw render result — a hiccup vector/seq, or a Ring response
map (when a handler returns {:status …}), which the caller passes
through unchanged.
Arguments:
(fn [error req] -> hiccup) error boundary(fn [render-fn] -> render-fn) applying the
render-middleware chain (use identity for none)The mount boundary is keyed on [handler path-params]: a page (re)mounts
on first render, on handler-identity change (navigation, Var redefinition),
and on a route path-param change (the same handler serving a different
path-param value). A superseded mount is unmounted first — its form-3 resource and
mount-scoped subviews (h/watch!, h/spawn!, async) are torn down, and the
new mount re-acquires whatever is scoped to the new path-params. Query-param
changes (filters, pagination — cursor-backed view state) re-render in place.
form-1 re-runs its body every render.
Resolve and render a page handler through the form-1/2/3 ladder, managing
the per-tab page-view lifecycle and the render purity guard.
Returns the raw render result — a hiccup vector/seq, or a Ring response
map (when a handler returns `{:status …}`), which the caller passes
through unchanged.
Arguments:
- app-state* — the app-state atom
- tab-id — the tab being rendered
- handler — the route's render fn (form-1/2/3 producer), already
re-resolved from live routes by the caller
- req — the request/context map
- render-error-fn — `(fn [error req] -> hiccup)` error boundary
- wrap-mw — `(fn [render-fn] -> render-fn)` applying the
render-middleware chain (use `identity` for none)
The mount boundary is keyed on `[handler path-params]`: a page (re)mounts
on first render, on handler-identity change (navigation, Var redefinition),
and on a route **path-param** change (the same handler serving a different
path-param value). A superseded mount is unmounted first — its form-3 resource and
mount-scoped subviews (`h/watch!`, `h/spawn!`, async) are torn down, and the
new mount re-acquires whatever is scoped to the new path-params. Query-param
changes (filters, pagination — cursor-backed view state) re-render in place.
form-1 re-runs its body every render.(run-unmount! stored)Run a stored page-view's :unmount (form-3 only), with effects allowed. Swallows and logs exceptions so teardown never breaks a render or disconnect cleanup.
Run a stored page-view's :unmount (form-3 only), with effects allowed. Swallows and logs exceptions so teardown never breaks a render or disconnect cleanup.
(teardown-page-view! app-state* tab-id)Tear down a tab's page-view (running a form-3 :unmount) and remove it. Called from cleanup-tab! on disconnect.
Tear down a tab's page-view (running a form-3 :unmount) and remove it. Called from cleanup-tab! on disconnect.
(view {:keys [mount render unmount] :as spec})Construct a form-3 View from a map.
Keys:
(fn [resource req] -> hiccup). resource is the
value returned by :mount (nil when there is no :mount).(fn [] -> resource). Runs once when the view
mounts. Its return value is the resource threaded into
:render and :unmount.(fn [resource] -> any). Runs once when the view
unmounts (route change, re-mount, or tab disconnect).A View is a distinct record type — not a bare map — so page dispatch
can tell it apart unambiguously from a hiccup vector, a form-2 render
fn, or a Ring response map.
Construct a form-3 `View` from a map.
Keys:
- :render (required) `(fn [resource req] -> hiccup)`. `resource` is the
value returned by `:mount` (nil when there is no `:mount`).
- :mount (optional) `(fn [] -> resource)`. Runs once when the view
mounts. Its return value is the resource threaded into
`:render` and `:unmount`.
- :unmount (optional) `(fn [resource] -> any)`. Runs once when the view
unmounts (route change, re-mount, or tab disconnect).
A `View` is a distinct record type — not a bare map — so page dispatch
can tell it apart unambiguously from a hiccup vector, a form-2 render
fn, or a Ring response map.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |