Troubleshooting¶
Start from what you have: a symptom you saw (a view that will not update,
a caret that jumps), or an error Fresco raised, which carries a stable
:rf.error/… id. Fresco calls its errors complaints.
Start from a symptom¶
The most common ones:
| Symptom | Cause | Fix |
|---|---|---|
| A view does not re-render when app-db changes | The value was not read with h/sub during the view's render (for example an explicitly framed rf/subscribe-once, or a value read once and kept in a callback) |
Read it with h/sub in the view body |
A view called as (todo-row {:id 7}) ignores its props, or fails with React's invalid-hook error |
A defview is a React component that only React may call; Fresco raises nothing for this |
Write [todo-row {:id 7}]; use a plain defn for a helper you call |
A dispatch from a timeout or promise raises :rf.error/no-frame-context |
The callback runs after rendering, with no frame in scope | Capture the frame with (rf/capture-frame) while rendering and use its :dispatch, or dispatch an effect that carries the frame |
| A controlled field drops characters or moves the caret | The edit was dispatched asynchronously, or the field left Fresco's controlled path | Dispatch the edit event directly from :on-input |
| Clearing a field does nothing | The model value did not change, so the field saw nothing to update | Advance ::h/revision when you reset it |
| List rows keep the wrong state after reordering | Rows are keyed by index, or not keyed | Put a stable id in :key |
Clicking an href="#" link jumps to the top of the page |
Fresco prevents the default only for :on-submit |
Wrap the event: [::h/prevent [:todo/set-showing :done]] |
Every chapter ends with its own troubleshooting table. Go to the chapter for what you were working on:
| Working on | Table |
|---|---|
| Getting a project to build or boot | Installation, Getting started |
| Views, reads, and what re-renders | Views and reads, Lists and collections |
| Event vectors and callbacks | Events as data |
| Text fields, carets, and IME | Controlled inputs, Forms |
| URLs and navigation | Routing and navigation |
| Fetching, mutations, and races | Async resources |
| A foreign React library | Interop |
| A React island | Islands |
| Local UI state | Ephemeral state |
| Animation and enter/exit | Motion and presence |
| Modals, popovers, and focus | Overlays and focus |
| Theme and locale | Theming and internationalisation |
| Tests | Testing |
| Xray and evidence | Diagnostics |
| Error boundaries | Errors |
| Server rendering and hydration | SSR and hydration |
| Something being too slow | Performance |
| A Reagent codebase | Migrating from Reagent |
| Lazy loading and chunks | Code splitting and lazy loading |
| Keyboard, screen readers, and roles | Accessibility |
Start from a complaint¶
A Fresco error is a thrown ex-info. Its message is the reason with the id in
brackets, and the id is in ex-data:
(try
(render-the-thing)
(catch :default e
(let [{:rf.error/keys [id] :keys [where reason]} (ex-data e)]
(js/console.error id where reason))))
Every error Fresco raises carries four keys, except evidence/envelope's
refusal, which carries no id:
| Key | What it tells you |
|---|---|
:rf.error/id |
Which error this is. Branch on this and nothing else |
:where |
Which function refused |
:reason |
Why, in a sentence, for a human |
:recovery |
Fresco's own refusals say :no-recovery, and the fix is in :reason; ids raised through core name a specific recovery. Branch on the id, not on this |
In a development build, errors raised while a view renders also carry :view
and :source: the view and the file and line of its defview. They are absent
in a release build, so use them for debugging and never branch or assert on
them.
In tests and error monitoring, match on the id, never the message. Messages improve between releases; an id never changes and is never reused.
The complaint index¶
Every error the package raises, grouped by the feature that raises it. Ids that core, routing or the SSR module define say which one defines them.
If an id is not here, it is raised by another part of re-frame2 (core, routing, resources), or your application and test-kit versions do not match.
Hiccup, heads and children¶
Taught in Views and reads.
:rf.error/fresco-empty-vector¶
You wrote [] where hiccup was expected. Give the vector a head, or write
nil where you meant nothing.
:rf.error/fresco-bad-head¶
You put something in hiccup head position that is not a tag keyword, :<>,
:>, a defview view or a defhost host — most often a plain function, or a
raw React component that needs defhost or [:> Component …]. To use a plain
function, call it; to make it a head, make it a defview.
:rf.error/fresco-true-child¶
You let true reach child position, usually a predicate result such as
(= id selected) written as a child. Return markup or nil instead, for
example with when.
:rf.error/ui-tree-malformed¶
You let a value outside the structural-tree grammar reach an L2 tree or a projection. Defined by the SSR module.
Fix the template or the runtime value, which the message names.
Key warnings¶
These two are development-build console warnings rather than thrown complaints. Each fires once per site. Taught in Lists and collections.
:rf.warning/fresco-missing-key¶
A seq of view children crossed into a boundary with no :key. React's own
missing-key check does not run on that path, so the list reconciles by index and
row state follows the wrong row when the order changes. Key each child on a
stable domain id: [child {:key (:id entity) …}].
:rf.warning/fresco-entity-key¶
A view child's :key is a map, vector or foreign object rather than a stable
identifier. React turns it into a string, so the child remounts whenever the
entity changes, and every foreign object collapses to [object Object]. Key on
the entity's id instead.
Reads and the render extent¶
h/sub works only while a view body is running, because that is when Fresco
records what the view reads. These complaints mean a read happened outside that
window, or the body did something a body may not do.
Taught in Views and reads.
:rf.error/fresco-sub-outside-render¶
You called h/sub outside a view body, for example in a callback, a
promise or a lazy sequence realised later. Read the value in the body and pass
or close over the result.
:rf.error/ambient-frame-refused¶
You called rf/subscribe or rf/dispatch in a view body without naming a
frame. Read with h/sub; dispatch through an event vector, h/event, or the
:dispatch of (rf/capture-frame). Defined by core.
:rf.error/fresco-deferred-read-at-boundary¶
You let an unforced delay reach a boundary's props. The child would force it
during its own render, the delay would cache the value, and the read would be
lost on the next render.
Pass a function instead: the child calls it on every render, so its reads stay tracked.
:rf.error/fresco-generation-fence-exhausted¶
A boundary body saw a new commit land during each of four consecutive runs — usually because the body writes to app-db, directly or through a synchronous dispatch, every time it renders.
Move the write out of the render, into an event.
Roots¶
h/render! takes root options only. Frame configuration is written in the tree.
Taught in Installation.
:rf.error/fresco-frame-config-misplaced¶
You passed :frame or :initial-events in h/render!'s options. Put them on
the head that takes them: [h/frame-root {:id … :initial-events […]} …] to
create the frame, or [h/frame-provider {:frame …} …] to scope one that
already exists.
:rf.error/fresco-unknown-root-option¶
You passed h/render! options that are not a map, or a key other than
:hydrate? and :identifier-prefix. Pass a map holding only those two keys.
:rf.error/no-adapter-installed¶
A frame was built before an adapter was installed. Make
(rf/init! substrate/adapter) the first line of boot; in Node, call
(rf/init! ssr/adapter) before server/render. Defined by core.
Frames¶
A frame is carried, never looked up. These fire when something rendered or dispatched with no frame in scope, a frame head was given the wrong key, or a frame could not be built.
Taught in Events as data.
:rf.error/frame-root-given-frame¶
You gave h/frame-root a :frame key. frame-root creates a frame and takes
:id; to scope an existing frame, use h/frame-provider. Defined by core.
:rf.error/frame-provider-given-id¶
You gave h/frame-provider an :id key. frame-provider scopes an existing
frame named by :frame; to create one, use h/frame-root. Defined by core.
:rf.error/frame-provider-frame-absent¶
You scoped a frame that does not exist. Make it first — rf/make-frame, or an
h/frame-root above — or, when hydrating, make it before ssr/hydrate!.
Defined by core.
:rf.error/frame-root-reconfigured¶
You re-rendered a mounted h/frame-root with a different :id or options —
most often a reload that dropped :initial-events. Pass the same options every
render; to switch frames, change the React :key so the boundary remounts.
Defined by core.
:rf.error/initial-events-step-failed¶
An :initial-events step threw while a frame was being built, whether handed to
hm/mount! or hm/hydrate! or to an h/frame-root creating its frame. The
frame is torn down and nothing was mounted, so there is no handle to tear down.
Fix the event the error names in its :step-index and :event. Defined by
core.
:rf.error/no-frame-context¶
You rendered a Fresco boundary or island hook whose React context carries no frame, or dispatched from a timeout or promise with no frame in scope. Defined by core.
Nothing falls back to a default frame. Render inside an h/frame-root or
h/frame-provider, or capture the frame with (rf/capture-frame) while
rendering and use the captured :dispatch in the callback.
Intents and callback positions¶
An intent is an event vector at a handler position. These fire where the position cannot turn it into a dispatch.
Taught in Events as data.
:rf.error/fresco-intent-outside-boundary¶
An event vector was turned into a callback outside any view's render, for
example inside a function a foreign component calls later. Keep event vectors
in the Hiccup a view returns; inside a foreign callback, use h/event.
It is also raised at render when an overlay's :on-dismiss, or an
h/error-boundary's vector :on-error, has no frame above it. Mount the
region under h/frame-root or h/frame-provider, or give :on-error a
function.
:rf.error/fresco-intent-needs-the-event¶
You wrote an event vector that reads a DOM event (a ::h/value marker, say)
at a foreign callback whose first argument is a value rather than a DOM event.
Use h/event, which receives every argument the caller passed, in order.
:rf.error/fresco-malformed-prevent¶
You wrapped something other than exactly one event vector in ::h/prevent.
Write [::h/prevent [:todo/set-showing :done]].
Controlled inputs¶
Taught in Controlled inputs.
:rf.error/fresco-revision-not-controlled¶
You put ::h/revision on something that is not a controlled text field. It
belongs on an :input or :textarea with :value.
:rf.error/fresco-file-input-value-marker¶
You read ::h/value from a file input, where .value is a fake path such as
C:\fakepath\photo.jpg, not the files. Use h/event and read
(.. e -target -files).
Error boundaries¶
Taught in Errors.
:rf.error/fresco-boundary-unknown-prop¶
You wrote a key outside h/error-boundary's closed roster — a misspelled
:on-error is an error boundary that reports nothing. Use only :fallback,
:reset-key and :on-error.
:rf.error/fresco-boundary-bad-on-error¶
You gave h/error-boundary an :on-error that is neither an intent vector nor
a function, so nothing could fire it. Pass an event vector such as
[:todo/record-failure], or a function.
Hosts and the raw escape¶
A defhost declaration is checked when the namespace loads. The raw [:>]
escape has no declaration, so its error is raised where it renders.
Taught in Interop.
:rf.error/fresco-host-no-component¶
You declared a defhost over nil, usually a JavaScript import that
resolved to nothing. Check the import name and whether it is a default export.
:rf.error/fresco-bad-host-declaration¶
You wrote a defhost declaration outside its shape, and the reason names
which: options that are not a map (usually a docstring written after the
component instead of before it); an option outside #{:callbacks :slots :server
:fallback}; a :callbacks contract outside :event and :render; a :slots
value that is not a set of ordinary prop names (a non-set, an entry that names
no prop, key/ref, one slot spelled twice, or a position that is also a
declared callback); or a form after the options map, which is discarded rather
than merged. Write the declaration as
(h/defhost name docstring? component opts?) and correct the part the reason
names.
:rf.error/fresco-host-bad-ssr-policy¶
You gave a defhost a :server value outside the two it admits, or a
:fallback the policy beside it cannot carry. Declare :server :client-only
(the default), optionally with a :fallback, or :server :render with no
:fallback.
:rf.error/fresco-host-fallback-boundary-head¶
You put a defview or defhost head inside a declared fallback.
Plain hiccup in the fallback, or :server :render to render the real subtree on
the server.
:rf.error/fresco-host-unclaimed-callback¶
You wrote h/event at a defhost prop declared in :slots. A slot takes
markup, not a function.
Write the markup there, or take the position out of :slots.
:rf.error/fresco-raw-not-a-component¶
You handed the raw escape nil in component position — usually a :default
import that resolved nothing — or a Fresco defview or defhost head, which
is a head in its own right.
Write [:> Component props & children] with the real component, or write the
head as [my-view …]. Any other invalid type is React's own error at render.
Routing¶
Taught in Routing and navigation.
:rf.error/fresco-route-link-outside-boundary¶
You rendered a route link outside any frame. Render it inside a view under an
h/frame-root or h/frame-provider.
:rf.error/fresco-route-link-bad-on-click¶
You gave a route link an :on-click that is not nil, a
[::h/prevent [:some/event …]] veto, an h/event, or a plain function. A bare
intent vector is refused because the click already dispatches the navigation.
To replace the navigation with your own event, wrap it:
[::h/prevent [:some/event …]].
:rf.error/fresco-route-link-claimed-intent-position¶
You gave a route link :prefetch :intent and also your own value at
:on-mouse-enter, :on-focus or :on-touch-start — the three positions
:prefetch fills. Drop :prefetch and write the prefetch
yourself at the
positions you are not using, or move your handler off those positions.
:rf.error/route-link-bad-prefetch¶
You gave a route link a :prefetch value other than :intent. To make the
link passive, omit :prefetch. Defined by routing, which raises it for both
h/route-link and rf/route-link.
:rf.error/routing-artefact-missing¶
You rendered a route link with routing absent. Defined by core.
Add day8/re-frame2-routing to your dependencies and require re-frame.routing
at boot, before frames are constructed.
Server rendering¶
Taught in SSR and hydration.
:rf.error/ssr-missing-payload-policy¶
You called server/render without :payload. Pass an allowlist vector of
top-level app-db keys, or :rf.ssr.payload/whole-app-db to send everything.
Defined by the SSR module.
:rf.error/ssr-render-failed¶
server/render or server/render-body completed, but the runtime recorded an
error it recovered from during the pass — a subscription that threw, say — so
the markup is not trustworthy. Fix the surface the error record names. Defined
by the SSR module.
:rf.ssr/hydration-mismatch¶
A development warning trace, not a throw. A hydrating root's first client render differed from the server markup, and React replaced that root's DOM. Keep view bodies deterministic, and put every value both sides render in the payload.
Motion and presence¶
Taught in Motion and presence.
:rf.error/fresco-presence-child-unkeyed¶
You gave motion/presence a child with no :key, or a child that is not a
hiccup vector. Write each child as a keyed hiccup vector, such as
[:li {:key id} …].
:rf.error/fresco-presence-timeout-required¶
You gave motion/presence no :timeout-ms, or one that is not a positive
number. Pass a positive :timeout-ms that covers your exit transition.
Overlays and focus¶
Taught in Overlays and focus.
:rf.error/fresco-overlay-anchor-missing¶
You gave an overlay an :anchor naming a DOM id no element in the document
carries. Omitting :anchor is fine: a modal takes none, and a popover without
one uses the default position.
Generate a unique, stable trigger id from the instance id, and render the trigger in the same tree as the overlay so the two arrive in one commit.
Ephemeral state¶
Taught in Ephemeral state.
:rf.error/fresco-state-bad-argument¶
You gave h/reg-state a concern that is not namespace-qualified, or options
outside {:default …}; or you used an instance key outside the accepted set
(nil included) at a read or a write. The reason names which. Use a qualified
keyword concern, options holding only :default, and an instance key that is a
keyword, string, number or vector of those.
The test kit¶
L2 runs one view body as a data tree with no React running. It throws when the thing being tested is not visible at that level; the fix is usually to test at the next level up (L3, mounted).
Taught in Testing.
:rf.error/fresco-test-not-a-body¶
You gave an L2 tree form a head that is not a defview body. Put a
defview view, or the body function it is defined from, in head position.
:rf.error/fresco-test-not-a-render-form¶
You gave an L2 tree something other than a hiccup form. Pass a non-empty
vector, [view props & children].
:rf.error/fresco-test-plain-fn-head¶
You put a plain function in a hiccup head inside an L2 tree. Define it with
h/defview, or pass it as the root form of ht/tree to run that body alone.
:rf.error/fresco-test-boundary-body-not-retained¶
You gave an L2 tree a defview head in a build that erased its body. Run
view tests in a development build, or pass the body function instead of the
head.
:rf.error/fresco-test-bad-option¶
You gave a test-kit door non-map options, or an option outside its closed
roster: #{:subs} for an L2 tree; :initial-events, :images,
:container and :clock for hm/mount!; those and :html for
hm/hydrate!, whose promise rejects with it rather than throwing; and
:reference, :candidate, :images, :initial-events and :script for
hm/shadow!. hm/shadow! also raises it for a script step other than
{:click selector} or {:type [selector text]}. Remove the key or step the
message names. hm/advance-clock! raises it on a handle whose hm/mount! or
hm/hydrate! was not given {:clock true}, or whose mount has come down; pass
{:clock true} to the mount that makes the handle.
:rf.error/fresco-test-bad-reads¶
You gave an L2 tree a :subs option that is not a query-to-value map. Pass a
map from query vector to value, such as {[:todo/by-id 7] {:title "Milk"}}.
:rf.error/fresco-test-missing-read-fixture¶
You let an L2 body read a subscription no fixture answers. Add a fixture for the exact query vector the message names.
:rf.error/fresco-test-host-is-opaque¶
You let a defhost crossing reach the L2 semantic tree. Test the view at L3
with hm/mount!.
:rf.error/fresco-test-react-is-opaque¶
You let a raw React element reach the L2 semantic tree. Test the view at L3
with hm/mount!.
:rf.error/fresco-test-not-a-host¶
You read the declared server policy off something that is not a defhost.
Pass ht/host-policy a var defined with h/defhost.
:rf.error/fresco-test-not-a-native-form¶
You gave an L1 projection a form whose head is not a tag keyword. Pass a
native form, such as [:input {:on-input [:todo/edit ::h/value]}].
:rf.error/fresco-test-not-an-intent¶
You gave the L1 marker materializer something other than an intent vector.
Pass ht/materialize an event vector.
:rf.error/fresco-test-not-a-dom-node¶
You gave the canonical-DOM comparator something that is not a DOM node. Pass
a DOM node, such as a mounted container; compare two L2 trees with =.
:rf.error/fresco-test-no-handler-at-position¶
You fired at a prop position the form does not write. Fire at one of the positions the message lists.
:rf.error/fresco-test-position-is-not-a-handler¶
You fired at a position whose value converts to something other than a
function. Fire at an on* prop that holds an event vector, a key map or a
function.
:rf.error/fresco-test-l1-dispatch¶
You invoked a handler converted by a pure L1 projection. Use ht/fire!, which
dispatches into a real frame, or test the handler at L3.
:rf.error/poll-until-timeout¶
The predicate given to hm/settle-until! never held before its :timeout-ms
(default 2000). The ex-data carries :elapsed-ms and your :label. Check that
the predicate can become true for what the mount renders, and raise
:timeout-ms only for work that is slow. hm/hydrate! rejects with it too,
when the root's adoption has not finished within the budget its third argument
sets (default 3000 ms); the ex-data then carries :elapsed-ms, :budget-ms
and the frame. Defined by core.