re-frame.fresco¶
Fresco is re-frame2's own view layer, and this namespace is what you write Fresco
views with: defview defines a view, sub reads a subscription inside it, and
client-root / render! mount it. Events, app-db, subscriptions and effects
are unchanged and stay on re-frame.core; Fresco changes only
how views are written.
Choose Fresco when you want views to stay data: markup is hiccup, a view calls
h/sub exactly where it needs a value, and most event handlers are event vectors
rather than closures, so tests and tools can read them off the rendered tree. For
a React-first screen, with hooks throughout and a React component library at its
centre, the UIx adapter is usually clearer, and an
existing Reagent app can stay on the Reagent adapter.
You can mix them: defhost mounts a React component inside a Fresco view, and
as-component mounts a Fresco view inside a React one. When Fresco
fits has the longer comparison.
(ns my-app.core
(:require [re-frame.core :as rf]
[re-frame.fresco :as h]
[re-frame.fresco.substrate :as substrate]))
(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)))
(h/defview counter [_]
[:button {:on-click [:counter/inc]}
"Clicked " (h/sub [:counter/value]) " times"])
(defonce app-root (h/client-root))
(defn ^:dev/after-load mount! []
(h/render! app-root
[h/frame-root {:id :app/main}
[counter]]
(js/document.getElementById "app")))
(defn run []
(rf/init! substrate/adapter)
(mount!))
rf/init! installs an adapter before anything mounts. Fresco's own is
re-frame.fresco.substrate; a Reagent or UIx
adapter works too.
Fresco runs in ClojureScript. On the JVM this namespace holds only its three authoring macros (see Authoring macros), and the optional modules do not exist. Fresco troubleshooting explains each Fresco error id with its fix; Errors says how to read a thrown id against a reported one.
The frame functions are core's: inside a view body, (rf/current-frame-id) and
zero-arity (rf/capture-frame) return the rendering view's frame, and this
namespace does not duplicate them.
Authoring macros¶
defview and defhost each expand to a def, so write them at the top level of a
namespace, never inside a view body. event is written inline, at a prop. On the
JVM these three macros are the whole namespace; the other twelve vars exist only in
ClojureScript.
defview¶
- Kind: macro
- Signature:
- Description: Defines a view: a React function component that is also a legal
hiccup head, so you render it as
[todo-row {:id 7}]. Fresco calls this independently re-rendering unit a boundary. The argument vector takes one props map, destructured as in any Clojure fn.- A
:keyin the props map goes to React and is removed from the props the body receives, so give each list item its domain id there. Children written after the props map arrive in the props at:children. - The macro does not inspect the body. It expands to a
defof the view plus a source coordinate, so an error raised while the body runs can name where the view was written. - The
fnit emits is anonymous, so it binds no name that could shadow a helper:(h/defview todo-row [p] (todo-row-body p))is safe. - The view is also registered in re-frame's
:viewregistry under(keyword "<ns>" "<sym>"), so a tool holding a keyword the author wrote can find the view: the entry holds it at:handler-fn, and(rf/view id)returns it. Registration happens only in development builds; a production build registers nothing. - Do not call React hooks in a body. A body is composed dynamically, so a hook's
call order would depend on the data. Put hook-heavy behaviour in a React
component mounted through
defhost, which the guide calls an island; seere-frame.fresco.native. - See Views and reads.
- A
- Errors, raised while the view renders:
:rf.error/no-frame-contextwhen noframe-rootorframe-provideris above the view.:rf.error/ambient-frame-refusedwhen the body callsrf/subscribe, orrf/dispatchwith no:frame: a read there would not re-render the view. Read withh/sub, and dispatch from an event prop, anh/eventor a handle from(rf/capture-frame).:rf.error/fresco-deferred-read-at-boundarywhen a child view's props carry an unforceddelay. Pass a function instead, or deref the delay in the body that wrote it.:rf.error/fresco-generation-fence-exhaustedwhen the body sees a new commit on four runs in a row, which a body that writes on every render causes. Move the write out of the render.
- Example:
defhost¶
- Kind: macro
- Signature:
- Description: Declares a React component from outside Fresco once, so you can
use it anywhere as a hiccup head, exactly like a view. Callback props are inferred
from each prop's name, as on a native tag: an
on*prop takes the shapes under Event props, and any other prop given anh/eventis a render callback.- The docstring goes before
component. Written after it, it is read asoptsand the declaration is refused. - Declaration errors are raised when the namespace loads.
- Other props cross shallowly. Keys are camelCased (
:on-row-clickbecomesonRowClick,:classbecomesclassName); a function crosses as itself; a map or vector goes throughclj->js; and a keyword or symbol crosses as itself, except at:class,:id,:role,data-*andaria-*, where it becomes its name. A component that expects the string"primary"must be passed the string, not:primary. - See Interop.
- The docstring goes before
- Options:
:callbacks:{prop :event|:render}, overriding the contract a prop's name implies. A vendor'son*-named render prop needs{:callbacks {:on-render-item :render}}. See Callback contracts.:slots: the set of props that take a React element. Hiccup written at one is converted under the writing view's frame; at an undeclared prop a hiccup vector is passed through as data. See ReactNode slots.:server::client-only(the default: the component renders nothing on the server or on hydration's first pass, and mounts once the page is adopted) or:render(you assert the component is safe to render on the server). See Server policy.:fallback: hiccup rendered in place of a:client-onlycomponent until it mounts.
- Errors:
:rf.error/fresco-host-no-component:componentis nil, usually a JS import that resolved nothing.:rf.error/fresco-bad-host-declaration:optsis not a map, has a key outside the four, or names a contract other than:eventor:render;:slotsis not a set of prop names, names:keyor:ref, spells one prop twice, or names a prop that:callbacksalso names; or a form followsopts. Nothing is silently dropped.:rf.error/fresco-host-bad-ssr-policy: a:servervalue outside the two, a:fallbackbeside:server :render, or:fallback nil.:rf.error/fresco-host-fallback-boundary-head: the:fallbackcontains adefviewordefhosthead. A fallback is plain markup.:rf.error/fresco-host-unclaimed-callback, at render: anh/eventat a prop named in:slots. A slot takes markup; write hiccup there, or take the prop out of:slots.
- Example:
;; DatePicker and Modal are React components required from npm. (h/defhost date-picker DatePicker) (h/defhost modal Modal {:slots #{:title :footer}}) ;; react-datepicker calls onChange(date, event), value first, so the prop ;; takes an h/event; a vector carrying ::h/value would raise ;; :rf.error/fresco-intent-needs-the-event. [date-picker {:selected due-date :on-change (h/event [date & _] [:task/set-due date])}]
event¶
- Kind: macro
- Signature:
- Description: Creates a callback, for a prop where you need the event object or
other arguments. It expands to a marked
fnand nothing else, so the value is an ordinary function.- Reach for it only when an event vector cannot say what you need. To pass the
input's value or checked flag, write
[:draft/set ::h/value], which needs no callback (see Event props). - Where it is written decides what its return value means. At an
on*prop a returned vector is dispatched and any other return is ignored. At any other prop of a native tag, adefhostcomponent or a[:>]crossing, it is a pure render callback and its return is the render output. At:ref, which Fresco leaves to React, it is a plain function. - See One callback form:
h/event.
- Reach for it only when an event vector cannot say what you need. To pass the
input's value or checked flag, write
- Example:
Reads¶
sub¶
- Kind: function
- Signature:
- Description: Returns the current value of the subscription
query-v. Call it anywhere inside a view body, including inside awhen, aforor a plain helper function the body calls; the view records the dependency where the read happens, so a branch not taken adds none.- It reads in the frame of the view that is rendering.
- Outside a view render (in an event handler, a callback or a utility) it raises
:rf.error/fresco-sub-outside-render, naming the query. For a one-off read there, userf/subscribe-once. - An unregistered query reads
niland emits:rf.error/no-such-sub, and a subscription whose body throws readsniland emits:rf.error/sub-exception. Neither throws into the view. - See Where
h/submay run.
- Example:
Frame boundaries¶
The frame is set in the tree, not by render!. frame-root or frame-provider
puts a frame in context for everything below it, whether at the root of a page or
around a subtree inside one. frame-root is the same component rf/frame-root and
re-frame.adapter.uix/frame-root mount, and both heads write the one React context
every adapter reads, so a Fresco tree is written like a Reagent or UIx one and a
Fresco subtree under a UIx provider resolves the same frame.
Use frame-root where a frame is declared, usually once at the root of the app: it
creates the frame and holds its options. Use frame-provider everywhere else a
subtree needs a frame that already exists. Do not declare a live frame a second
time with frame-root to reach it: a second frame-root on the same :id
replaces that frame's options wholesale, so a partial options map drops what the
first one set. See What the boot creates
and frame-provider and frame-root.
frame-root¶
- Kind: component (Fresco head)
- Signature:
- Description: Creates the named frame if it is absent, or reuses it without
re-seeding if it is live, and provides it to the subtree.
- The opts map is the whole
rf/make-frameoption map::id(required), plus:doc,:fx-overrides,:url-bound?and the rest. Frame options go here rather than into a separatemake-framecall before the mount. :initial-eventsrun once, when the frame is first created. A hot reload or remount under the same:idkeeps the frame's state and does not run them again.- The frame is created at commit, which is why
frame-rootis the wrong head for hydrating server-rendered markup; useframe-providerthere (seerender!). - Unmounting destroys nothing: the frame outlives the head, and
rf/destroy-frame!ends it.
- The opts map is the whole
- Errors:
:rf.error/frame-root-missing-idwhen:idis missing or not a keyword.:rf.error/frame-root-given-frameon a:framekey, namingframe-provider.:rf.error/frame-root-reconfiguredwhen a mounted head's:idor opts change. To point the subtree at a different frame, give the head a React:keythat changes, which remounts it.
- Example:
frame-provider¶
- Kind: component (Fresco head)
- Signature:
- Description: Scopes a subtree to a frame that already exists; it creates,
refreshes and destroys nothing.
- Use it after
re-frame.ssr/hydrate!, for a second root on a frame another root already created, and around a subtree that works on another frame, such as a preview pane. :frameis a frame id keyword or the live frame valuerf/make-framereturns.- Everything written below the head uses its frame: event props,
h/eventcallbacks and child views. Calls the enclosing body makes itself (h/sub,h/route-link,rf/capture-frame) run before the head takes effect and stay in the body's frame, so read the scoped frame from a child view under the provider.
- Use it after
- Errors:
:rf.error/frame-provider-frame-absentwhen the frame does not exist.:rf.error/no-frame-contexton a nil:frame.:rf.error/bad-frame-provider-argon a:framethat is neither a keyword nor a frame value; a destroyed frame's value raises:rf.error/frame-provider-frame-absent.:rf.error/frame-provider-given-idon an:idkey, namingframe-root.
- Example:
Roots¶
A browser app needs a React root that is created once, updated on every hot
reload and released on teardown. client-root, render! and unmount! manage
it, with the same names and the same create-then-update behaviour as on the
Reagent and
UIx adapters. Each function acts only on
the root whose handle you pass, so a page can hold as many roots as it needs. See
What the boot creates and
Hot reload.
client-root¶
- Kind: function
- Signature:
- Description: Returns a new, inert client-root handle. It does no DOM work and
makes no React call, so it belongs under a
defonceat namespace load; the firstrender!through the handle creates (or hydrates) the React root.- The handle is opaque: pass it to
render!andunmount!and nothing else. The raw React root is not reachable through it. - A handle holds at most one React root at a time, so two roots on a page need two handles.
- The handle is opaque: pass it to
- Example:
render!¶
- Kind: function
- Signature:
- Description: Renders
viewinto the DOM nodecontainerthroughhandle: the first call creates the React root, and every later call updates it insideflushSync. Returns nil. On a hot reload the root, its frame and the frame'sapp-dbcarry on, but a re-evaluateddefviewis a new component type, so React remounts it and everything beneath it: that DOM is rebuilt, and focus, the caret and scroll position start over.containerandoptstake effect on the first call only, thoughoptsis checked on every call.viewis the whole root tree, frame head included:[h/frame-root {:id …} …]or[h/frame-provider {:frame …} …]. Render the same head with the same options on every call: build the tree in one function and call it from everyrender!. Re-passing:initial-eventsis harmless, since they run once per frame.- A later render that drops the head leaves the subtree with no frame in context, and switching to the other head is a React type change that remounts everything.
- A hydrating tree uses
frame-provider.frame-rootcreates its frame at commit, so its first render has no children, while an adopting root must render the server's markup on its first pass;frame-providerrenders its children immediately, so the shapes agree. - Neither hydration step creates the frame:
re-frame.ssr/hydrate!dispatches:rf/hydrateat a frame that must already exist. Make the frame first, install the payload second, and adopt the DOM third. See Create the frame, hydrate state, then adopt the DOM.
- Options:
:hydrate?:truemakes the first call adopt the server-rendered DOM already incontainer(hydrateRoot) instead of replacing it, with its own recoverable-error reporter in development builds. It returns before adoption finishes. A later call through a live handle ignores it.:identifier-prefix: passed to React asidentifierPrefix. A hydrating root must be given the prefix its server render used.
- Errors:
:rf.error/fresco-frame-config-misplaced:optscarries:frameor:initial-events. The message names the head that takes them.:rf.error/fresco-unknown-root-option: any otheroptskey, or anoptsthat is not a map.:rf.error/frame-root-reconfigured: a later render changes theframe-root's:idor options, for example by dropping:initial-eventsbecause they have already run.
- Example:
(h/render! app-root [h/frame-root {:id :rf/default :initial-events [[:counter/initialise]]} [counter]] (js/document.getElementById "app")) ;; Hydrating a server-rendered page (ssr is re-frame.ssr): (rf/make-frame {:id :app/main}) ;; 1. frame (ssr/hydrate! {:frame :app/main}) ;; 2. state (h/render! app-root ;; 3. DOM [h/frame-provider {:frame :app/main} [page]] (js/document.getElementById "app") {:hydrate? true :identifier-prefix "main"})
unmount!¶
- Kind: function
- Signature:
- Description: Unmounts the React root
handleholds and returns the handle to inert, so a laterrender!through it mounts afresh. Returns nil.- Idempotent. Sibling roots' subscriptions and frames are untouched, and the container stays in the document; React empties it but does not remove it.
- It destroys no frame, including this root's own: a frame outlives the root
that created it, and
rf/destroy-frame!ends it. rf/destroy-adapter!also releases a still-live handle's root, exactly once, after whichunmount!does nothing.
- Example:
Markup¶
error-boundary¶
- Kind: component (React class component, Fresco head)
- Signature:
- Description: Catches an error thrown while rendering anything below it and
renders
:fallbackin place of its children. It is React's error boundary, a class component, and not a Fresco view: it reads no subscriptions and uses no hooks.:fallbackis hiccup, or(fn [error] hiccup).- A change in
:reset-key(compared with=) clears the caught error and re-mounts the children. :on-errorfires once per caught error: an event vector is dispatched with the error appended, in the frame the boundary is mounted under, and a function is called with the error.- Without
:fallback, a caught error renders nothing where the children were. - It catches throws while rendering, and from lifecycles and effects, below it.
A throw from an event handler, a timer or another callback goes to the
browser's error channel, and a throw while rendering
:fallbackgoes to the next boundary up. - See Errors.
- Errors:
:rf.error/fresco-boundary-unknown-propon any other key, so a misspelt option cannot leave a boundary that silently reports nothing.:rf.error/fresco-boundary-bad-on-errorwhen:on-erroris neither an event vector nor a function (a bare keyword included).:rf.error/fresco-intent-outside-boundarywhen:on-erroris a vector and no frame is in scope. Mount the boundary under a frame, or pass a function.
- Example:
The cell below runs this boundary. Break the chart and the fallback replaces it, and
:on-error records the failure. Show sales and the chart comes back, because
:reset-key changed; remove :reset-key and run the cell again, and the fallback
stays.
(require '[re-frame.core :as rf]
'[re-frame.fresco :as h])
(rf/reg-event :chart/set-query
(fn [{:keys [db]} [_ query]]
{:db (assoc db :chart/query query)}))
(rf/reg-event :chart/render-failed
(fn [{:keys [db]} [_ error]]
{:db (assoc db :chart/last-failure (ex-message error))}))
(rf/reg-sub :chart/query (fn [db _] (:chart/query db :sales)))
(rf/reg-sub :chart/last-failure (fn [db _] (:chart/last-failure db)))
(h/defview chart-panel [{:keys [query]}]
(if (= query :broken)
(throw (ex-info "no data for :broken" {:query query}))
[:p "Chart of " (name query)]))
(h/defview dashboard [_]
(let [query (h/sub [:chart/query])]
[:div
[:button {:on-click [:chart/set-query :broken]} "Break the chart"]
" "
[:button {:on-click [:chart/set-query :sales]} "Show sales"]
[h/error-boundary {:fallback (fn [e] [:p.error "The chart failed: " (ex-message e)])
:reset-key query
:on-error [:chart/render-failed]}
[chart-panel {:query query}]]
[:p "Last failure recorded: " (pr-str (h/sub [:chart/last-failure]))]]))
[h/frame-root {:id :app/charts}
[dashboard {}]]
portal¶
- Kind: component (Fresco head)
- Signature:
- Description: Renders its children into the DOM node
:target, through React'screatePortal. Use it for a container the application does not own; for a popover or dialog, usere-frame.fresco.overlay, which handles anchoring, dismissal and focus without a portal.- Events bubble through the React tree, so an ancestor's
:on-clicksees clicks inside the portalled subtree, and event vectors inside it dispatch in the writing view's frame. - A changed
:targetis a remount, so keep it stable rather than looking it up on every render. A:targetthat is not a DOM node is React's own error at render. - It is client-only: the subtree is absent from a server response, and
:fallbacktakes its tree position there. - See Portals.
- Events bubble through the React tree, so an ancestor's
- Example:
route-link¶
- Kind: function
- Signature:
- Description: Returns a real
<a>for a route, as hiccup; thehrefand the click handling come from re-frame2's routing. A plain left-click navigates in the frame that rendered the link; modifier clicks, and links with:targetor:download, are left to the browser (seeroute-link).- It is a plain function, not a view: call it, rather than writing it as a head. It adds no boundary and no hook, and it must be called inside a view render.
:tonames a registered route;:params,:queryand:fragmentbuild thehref.:replace?,:scrolland:bypass-leave?apply to the navigation the click makes, as they do on:rf.route/navigate. Every other key except:prefetchand:on-clickpasses through to the<a>, so:classand:aria-currentwork as usual.:on-clickis a veto that runs before the navigation:nil,[::h/prevent [:app/event]](cancel the navigation and dispatch this instead), anh/eventor a plain function. A bare event vector is refused, because the click already produces the routing event.:prefetch :intentdispatches[:rf.route/prefetch …]for the link's route from:on-mouse-enter,:on-focusand:on-touch-start. Leaving the key out is the only way to opt out.- See Render an application route link.
- Errors:
:rf.error/routing-artefact-missingat render whenre-frame.routingis not loaded, naming the:to.:rf.error/fresco-route-link-outside-boundarywhen called outside a view render.:rf.error/fresco-route-link-bad-on-clickon an:on-clickoutside the four shapes above.:rf.error/route-link-bad-prefetchon any:prefetchvalue other than:intent.:rf.error/fresco-route-link-claimed-intent-positionwhen:prefetch :intentis combined with your own:on-mouse-enter,:on-focusor:on-touch-start. Drop:prefetchand dispatch the prefetch by hand from the positions you are not using.- The address errors of
route-url, at render::rf.error/no-such-routefor an unregistered:to,:rf.error/missing-route-paramfor a missing path param, and the validation errors listed there.
- Example:
as-element¶
- Kind: function
- Signature:
- Description: Converts hiccup to a React element, under the frame of the view
currently rendering. Use it where Fresco does not convert hiccup for you.
- A declared
:rendercallback's return value reaches the foreign component unconverted, so a hiccup vector returned there makes React throw. - A
[:>]crossing has no:slots, so a prop that takes an element needs one. - A child handed to a native React subtree needs one, because a hiccup vector is refused there.
- Where the crossing is declared, prefer
defhost's:slots, which converts those props at every use site. - Event vectors in the converted markup keep dispatching in the frame of the view
that supplied them. Outside any view render it still converts, but an event
vector or key map in the markup raises
:rf.error/fresco-intent-outside-boundary. - See Render positions.
- A declared
- Example:
as-component¶
- Kind: function
- Signature:
- Description: Returns a real React component for a Fresco view, so a UIx or
plain-JavaScript parent can mount it under the frame it is already in. Define it
once at top level, beside the view: each call makes a new component type, so a
call during render would remount the subtree every time.
- The parent's props arrive as the view's ordinary props map, with camelCase
names turned back into keywords (
articleIdbecomes:article-id) and values passed as they are; its children arrive at:children. - The frame comes from React context, written by any adapter's frame head, so no
second root or state owner is involved. Rendered outside every frame, it raises
:rf.error/no-frame-context. - See Render a Fresco view from native React.
- The parent's props arrive as the view's ordinary props map, with camelCase
names turned back into keywords (
- Example:
Local state¶
reg-state¶
- Kind: function
- Signature:
- Description: Registers per-instance UI state for
concern: one parametric subscription and one setter event, stored inapp-dbat[:ui concern instance-key], plus the shared::h/clearevent that returns an instance to its default. Returnsconcern. Use it for a value that means nothing beyond itself, such as a disclosure's open flag or a tab strip's selection; when a change must trigger something else or be recorded, write a named event instead.- Read with
[concern instance-key], write with[concern instance-key value], and clear with[::h/clear concern instance-key]. An absent entry reads as:default. concernmust be a namespace-qualified keyword, since it is a subscription id, an event id and anapp-dbkey at once.optstakes only:default.- The instance key is a keyword, string, number, or a vector of those; derive it
from data, the way you would a React
:key.nilis refused, because it is what a missing prop evaluates to and would make every instance share one value. - Registering the same concern again replaces all three registrations, so the
last
:defaultwins. - See Application-visible state.
- Read with
- Errors:
:rf.error/fresco-state-bad-argument, thrown byreg-state, for an unqualified concern, or options that are not a map or carry a key other than:default. The message names the fault.- A bad instance key raises the same id inside the subscription or event, where
the runtime catches it: a read emits
:rf.error/sub-exceptionand readsnil, and a write or clear emits:rf.error/handler-exceptionand changes nothing.
- Example:
Optional modules¶
The optional modules are separate namespaces, and this one requires none of them,
so an app that never requires one carries none of its code. presence, for
example, is re-frame.fresco.motion/presence, not h/presence. Each of these has
a page here:
.forms: a text field that edits a draft and commits it on Enter or blur..motion: keeping children rendered while they animate out..native: hooks for a React component mounted inside a Fresco tree..overlay: popovers and modal dialogs on the browser's top layer..substrate: Fresco's own adapter, forrf/init!.
The .server SSR module, the test kits and the tool namespaces are documented in
the Fresco API reference.
Hiccup¶
A view body returns hiccup. A vector's head is one of:
| Head | What it renders |
|---|---|
a tag keyword, symbol or string, such as :span#total.label (the #id before any .class) |
a DOM element. .class shorthand joins :class, and #id applies when there is no :id |
:<> |
a React fragment, reading only :key and :ref from its props map |
a defview or defhost var, or a head from this namespace or an optional module |
that view or component |
:>, written [:> Component props? child …] |
an undeclared React component: defhost with no options, always client-only and with no fallback |
A child is hiccup, a string or number, a seq (its members splice in place), a
React element, or a keyword or symbol (rendered as its name). nil and false
render nothing. At a native tag, prop keys are camelCased except aria-*,
data-* and --custom properties; :class takes a string, keyword or collection
of those; a map value such as :style gets camelCased keys; and :ref reaches
React untouched. See Props, children, and
fragments and
Attribute conversion.
- Errors:
:rf.error/fresco-empty-vector:[]where hiccup is expected.:rf.error/fresco-bad-head: any other head, most often a plain function.:rf.error/fresco-true-child:trueas a child, usually a predicate result.:rf.error/fresco-raw-not-a-component:[:>]givennilor adefviewordefhostvar.
- Warnings, on the console in development builds, once per site:
:rf.warning/fresco-missing-key: a seq of views with no:key, passed as a view's children. See Use stable domain keys.:rf.warning/fresco-entity-key: a view child keyed by a map, vector or other object.
Event props¶
An event prop, named :on-<event> or React's :on<Event> (:on-click and
:onClick are the same prop), takes one of four shapes, on a native tag and on a
defhost component alike. Fresco's error ids call a vector or key map written at
an event prop an intent. See Events as data.
| Value | What happens |
|---|---|
an event vector, [:todo/toggle id] |
dispatched in the view's frame when the event fires. At :on-submit the browser default is also prevented. |
a key map, {"Enter" [:todo/commit id] "Escape" [:todo/cancel id]} |
the entry for the pressed key (spelled as the browser's KeyboardEvent.key) runs: a vector is dispatched, and a function or h/event is called. Presses during IME composition are ignored. See Keyboard maps. |
an h/event callback |
called with the event; a returned vector is dispatched. |
| a plain function | passed to React unchanged. |
- Errors:
:rf.error/fresco-intent-outside-boundary: an event vector or key map rendered with no frame in scope, or anh/eventat anon*prop that returns a vector with none. Render the markup under a frame head.:rf.error/fresco-intent-needs-the-event: a vector carrying::h/value,::h/checkedor::h/prevent, a vector at:on-submit, or a key map, at a prop whose caller passes something other than the DOM event first, such asonChange(date). Write anh/event, which receives every argument. A plain vector with no marker works at any prop.
Each control below uses one shape from the table, and the list shows the event
each one dispatched. Type in the field and press Enter: ::h/value arrives as the
text. The plain function dispatches through a handle from rf/capture-frame,
because React calls it with no frame in scope.
(require '[re-frame.core :as rf]
'[re-frame.fresco :as h])
(rf/reg-event :event-log/add
(fn [{:keys [db]} event]
{:db (update db :event-log/entries (fnil conj []) event)}))
(rf/reg-sub :event-log/entries (fn [db _] (:event-log/entries db [])))
(h/defview event-props [_]
(let [{:keys [dispatch]} (rf/capture-frame)]
[:div
[:button {:on-click [:event-log/add :vector]} "Event vector"]
" "
[:input {:placeholder "Type, then Enter or Escape"
:on-key-down {"Enter" [:event-log/add :enter ::h/value]
"Escape" [:event-log/add :escape]}}]
" "
[:button {:on-click (h/event [_e] [:event-log/add :h-event])} "h/event"]
" "
[:button {:on-click (fn [_e] (dispatch [:event-log/add :plain-fn]))}
"Plain function"]
[:ol (for [[i entry] (map-indexed vector (h/sub [:event-log/entries]))]
[:li {:key i} (pr-str entry)])]]))
[h/frame-root {:id :app/event-log}
[event-props {}]]
Marker keywords¶
::h/value, ::h/prevent, ::h/revision, ::h/checked and ::h/clear are
keywords in the re-frame.fresco namespace; with this namespace aliased as h,
write them as shown. See Read values from the browser
event,
Prevent browser defaults
explicitly
and Reset with ::h/revision.
| Keyword | Where it goes | What it does |
|---|---|---|
::h/value |
inside an event vector | replaced at dispatch time by the event target's current value, or a vector of the selected values on a <select multiple>. On a file input it raises :rf.error/fresco-file-input-value-marker; read .files in an h/event instead |
::h/checked |
inside an event vector | replaced by the target's checked flag |
::h/prevent |
as an event vector's head, wrapping another vector | calls preventDefault, then dispatches the wrapped vector: [::h/prevent [:filter/show-done]]. It wraps exactly one non-empty event vector and does not nest; anything else raises :rf.error/fresco-malformed-prevent |
::h/revision |
a prop on a controlled <input> or <textarea> |
a change resets the field to its :value, even when :value itself did not change. On any other native element it raises :rf.error/fresco-revision-not-controlled; at a view it is an ordinary prop |
::h/clear |
as an event id | [::h/clear concern instance-key] removes a reg-state instance, back to its default |
Substitution looks only at the top level of the vector: [:todo/edit id ::h/value]
works, and a marker nested in a map or an inner vector is left as it is.