Skip to content

Troubleshooting

Two things go wrong, and they arrive differently. A symptom is something you saw: a view that will not update, a caret that jumps, a fallback that never clears. A complaint is something Hicasso said: a thrown ex-info carrying a stable :rf.error/… id.

Start from whichever one you have.

Start from a symptom

Every chapter ends with a troubleshooting table for the surface it teaches, and that is where a symptom is worth looking first, because the table sits beside the mechanism that explains it. Go to the chapter that owns the surface 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
n/$ and native components The native tier
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 Hicasso complaint 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 recovery]} (ex-data e)]
      (js/console.error id where reason recovery))))

Four slots ride every complaint, and they answer four different questions:

Slot Question it answers
:rf.error/id Which refusal is this? Branch on this one and nothing else
:where Which function refused
:reason Why, in a sentence, for a human
:recovery What to do, as a keyword naming the fix

Two more, :view and :source, name the boundary that was rendering and the file and line its defview was written at. They are context, not contract: they are present in a development build inside a declaration or a render extent, and absent — not nil, absent — outside one and in a release build. Read them to help yourself; never branch on them and never require them in a test that must also pass against a production build.

Assert the id, never the message. Messages improve between releases; an id is frozen for the life of the refusal, never reused after it is retired, and is what an error monitor's grouping rule and your own tests should key on. The :recovery beside it is not frozen the same way: it is concrete advice about a live API, so it is rewritten when that API is renamed.

The complaint index

Every complaint the shipped package raises today, grouped by the surface that raises it. The normative meaning and payload of each id is spec/009-Instrumentation.md, and this page is the reader's route into it.

An id you cannot find here is either not Hicasso's or not from this version. Check the namespace first — core, routing and the resources model raise their own — and then check that your application and test-kit versions match. A few further spellings are claimed without being raised — reserved for surfaces not built yet, or dead forever — and they are listed under Ids that are claimed but not raised at the foot of this page.

Hiccup, heads and children

The interpreter refuses a value it would otherwise have to guess at. Where it can repair what you wrote it repairs it silently and there is no complaint here; these are the cases where repairing would mean overruling you.

Taught in Views and reads.

:rf.error/hicasso-empty-vector

You wrote [] where hiccup was expected.

Recovery: :supply-a-hiccup-head — a hiccup vector must have a head.

:rf.error/hicasso-bad-head

You put something outside the closed head set in hiccup head position.

Recovery: :call-it-or-make-it-a-view on the function-head arm; :supply-a-valid-hiccup-head otherwise.

Named in Views and reads, Lists and collections, Diagnostics.

:rf.error/hicasso-true-child

You let true reach child position.

Recovery: :use-nil-or-false.

Named in Views and reads.

:rf.error/hicasso-merge-not-a-map

You forwarded a non-map at the attribute-remainder key.

Recovery: :forward-a-map-at-the-merge-key — forward a map, or drop the key.

:rf.error/hicasso-ref-vector-reserved

You put a vector at the canonical ref slot.

Recovery: :use-a-callback-ref-or-an-effect — write the callback ref, or move the mechanic to an event and an effect.

:rf.error/ui-tree-malformed

You let a value outside the structural-tree grammar reach an L2 tree or a projection. This is a corpus id rather than a Hicasso one: the wider framework defines the spelling and Hicasso reuses it.

Recovery: :no-recovery in the general case — fix the template or the runtime value, which the message names. Each bridge arm and each test-kit arm carries its own instead, so read the one you were given rather than the family default.

Reads and the render extent

A subscription read is only meaningful while a boundary body is running, because that is the extent whose read set the runtime is recording. Every complaint here says the read left that extent, or that the body did something a body may not do.

Taught in Views and reads.

:rf.error/hicasso-sub-outside-render

You read a subscription outside a boundary body.

Recovery: :read-inside-a-boundary-body.

Named in Views and reads, Testing, Diagnostics.

:rf.error/hicasso-deferred-read-at-boundary

You let an unforced delay reach a boundary's props.

Recovery: :hand-a-function-or-deref-it-in-this-body — a function is called on every child render, so its reads are the child's edges and are kept.

Named in Views and reads, Testing, Diagnostics.

:rf.error/hicasso-generation-fence-exhausted

You wrote to app-db from a body, on four consecutive runs.

Recovery: :move-the-write-out-of-the-render — a body that writes on every render cannot be fenced.

:rf.error/hicasso-frame-outside-boundary

You asked for the frame with no Hicasso render extent in scope.

Recovery: :read-the-frame-inside-a-boundary-render.

Frames

A frame is carried, never inferred. When one of these fires, some value crossed a boundary that does not carry the frame with it.

Taught in Events as data.

:rf.error/no-frame-prop

You mounted a frame-fed boundary with no frame in its props.

Recovery: :mint-the-root-element-with-a-frame — the root element and any outward bridge name the frame they mint under.

:rf.error/no-frame-context

You rendered a Hicasso boundary whose React context carries no frame. This is a corpus id rather than a Hicasso one: the wider framework defines the spelling and Hicasso reuses it.

Recovery: :supply-frame — the op fails fast and is NOT routed to a synthesised default; the fix is to carry the frame explicitly (capture it as a value at render time and thread it into the callback, or pass {:frame …}).

Named in Events as data, Interop, The native tier, SSR and hydration, Migrating from Reagent.

Intents and callback positions

An intent is a vector that means dispatch this. It only means that where something is prepared to lower it, and these complaints mark the positions where nothing is.

Taught in Events as data.

:rf.error/hicasso-intent-outside-boundary

You lowered or fired an intent with no frame-locked dispatch bound.

Recovery: :lower-intents-inside-a-boundary-render.

Named in Diagnostics, Errors.

:rf.error/hicasso-dispatch-in-render-position

You dispatched from a render callback while it was running.

Recovery: :dispatch-from-an-event-position — move the dispatch to an event position, or to an event handler that owns the work.

Named in Events as data, Interop.

:rf.error/hicasso-intent-at-a-non-event-contract

You put an intent at a position declared :handler or :render.

Recovery: :declare-the-position-event-or-write-an-h-event — declare the position :event if what happens there is an event, or write an h/event that does the declared work.

Named in Events as data, Interop.

:rf.error/hicasso-intent-needs-the-event

You wrote an event-reading intent at a value-first foreign callback.

Recovery: :write-an-h-event-at-a-value-first-position — the one callback form receives every argument the invoker passed, in order.

Named in Events as data, Interop.

:rf.error/hicasso-unknown-callback-contract

You named a callback contract outside :event / :handler / :render.

Recovery: :declare-event-handler-or-render.

:rf.error/hicasso-malformed-prevent

You wrapped something other than exactly one intent vector in the prevent decorator.

Recovery: :wrap-exactly-one-intent-vector.

Named in Events as data.

:rf.error/hicasso-malformed-navigate

You wrote the navigate decorator outside its closed grammar.

Recovery: shape arm :carry-frame-payload-native-and-veto; veto arm :veto-with-prevent-a-callback-or-nothing.

Named in Routing and navigation.

Controlled inputs

A controlled field has one owner and one reset trigger. These complaints fire where a second owner was implied, or where the trigger was written at a position that cannot receive it.

Taught in Controlled inputs.

:rf.error/hicasso-revision-not-controlled

You put the reset trigger on something that is not a controlled text field.

Recovery: :put-the-revision-on-a-controlled-input-or-textarea.

Named in Controlled inputs, Forms, Diagnostics.

:rf.error/hicasso-revision-from-remainder

You let a forwarded attribute map introduce the reset trigger.

Recovery: :write-the-revision-as-a-literal-on-the-element — drive it from whatever the caller sent through an ordinary prop.

:rf.error/hicasso-file-input-value-prop

You put a non-empty :value on a file input, which the platform refuses and React writes anyway.

Recovery: :leave-the-file-input-uncontrolled-and-read-files-with-an-h-event.

:rf.error/hicasso-file-input-value-marker

You read ::h/value off a file input, where .value is the C:\fakepath\ fiction and the first file's name — not the files.

Recovery: :read-the-file-list-with-an-h-event.

Error boundaries

An error boundary that reports nothing looks exactly like one that never caught anything, so its props are a closed roster rather than a suggestion.

Taught in Errors.

:rf.error/hicasso-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.

Recovery: :write-fallback-reset-key-or-on-error.

:rf.error/hicasso-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.

Recovery: :hand-an-intent-vector-or-a-one-argument-function.

Hosts and the raw escape

A defhost declaration is validated once, at the declaration, rather than at every crossing. Most of this group therefore fires at load time and names the declaration; the raw [:>] escape has no declaration to validate, so its two fire at the crossing instead.

Taught in Interop.

:rf.error/hicasso-host-no-component

You declared a defhost over nil.

Recovery: :hand-the-declaration-a-real-component.

:rf.error/hicasso-host-bad-options

You gave a defhost declaration options that are not a map — usually a docstring written after the component instead of before it.

Recovery: :pass-a-map-of-options.

:rf.error/hicasso-host-extra-form

You wrote a form after defhost's options map — a second options map is not merged, it is discarded.

Recovery: :write-one-options-map-and-put-any-docstring-before-the-component.

:rf.error/hicasso-host-unknown-option

You gave a defhost declaration an option outside its roster #{:callbacks :slots :server :fallback} — the retired :ssr spelling included.

Recovery: :declare-callbacks-slots-server-or-fallback.

Named in Interop, SSR and hydration.

:rf.error/hicasso-host-bad-slots

You declared a defhost :slots that is not a set of ordinary prop names — a non-set, an entry that names no prop, key/ref, a name the crossing can never emit (__proto__, prototype, constructor), one slot spelled twice, or a position that is also a declared callback.

Recovery: :declare-slots-as-a-set-of-ordinary-props.

:rf.error/hicasso-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.

Recovery: :declare-render-or-client-only-with-an-optional-fallback.

Named in Interop, SSR and hydration.

:rf.error/hicasso-host-fallback-boundary-head

You put a defview or defhost head inside a declared fallback.

Recovery: :write-inert-hiccup-or-declare-server-render — plain hiccup in the fallback, or :server :render to render the real subtree on the server.

Named in Interop, SSR and hydration.

:rf.error/hicasso-host-structural-callback

You declared a defhost callback contract at a position no contract can reach — key/ref, which carry no contract, or a name the crossing can never emit (__proto__, prototype, constructor).

Recovery: :declare-contracts-on-ordinary-props-only.

:rf.error/hicasso-host-callback-slot-collision

You declared two spellings of one callback slot on a defhost.

Recovery: :declare-each-slot-once.

:rf.error/hicasso-host-undeclared-callback

You sent an intent to a defhost prop the declaration does not name.

Recovery: :declare-the-callback-contract — name the prop in :callbacks, or hand a plain function.

Named in Interop, Migrating from Reagent.

:rf.error/hicasso-host-unclaimed-callback

You wrote the one callback form at a defhost position no callback contract claims — one nothing claims at all, or one declared a ReactNode slot, where markup lowers and there is no contract to give a function.

Recovery: :declare-the-slot-or-hand-a-plain-function.

Named in Events as data, Interop, Diagnostics.

:rf.error/hicasso-raw-no-component

You handed the raw escape nil in component position.

Recovery: :hand-the-escape-a-real-component — write [:> Component props & children], or declare the crossing with defhost.

Named in Interop.

:rf.error/hicasso-raw-not-a-component

You handed the raw escape a value React will not mint a fiber for.

Recovery: :hand-the-escape-a-component-react-accepts — a function or class component, a React built-in wrapper, or a memo / lazy / forwardRef / context value.

Named in Interop.

Portals

A portal renders into a container you name, so the one thing it cannot do without is that container.

Taught in Interop.

:rf.error/hicasso-portal-no-target

You gave h/portal a :target that is not a DOM container — usually a lookup that answered nothing.

Recovery: :give-the-portal-a-dom-container-that-exists — render the portal only once the container is there, or point :target at a node that outlives the page, such as js/document.body.

The native tier

Past n/$ the brackets stop meaning hiccup and the vector stops meaning an intent. Every complaint here is a value written in the interpreted language on the far side of that fence.

Taught in The native tier.

:rf.error/hicasso-native-map-as-child

You wrote a dynamic map in native props position, where it lands as a child.

Recovery: :mark-the-props-operand-with-n-props — write (n/props m) where the map is meant as props.

:rf.error/hicasso-native-hiccup-child

You put a hiccup vector in a native child position, where brackets have no meaning.

Recovery: :nest-n-dollar-or-convert-with-h-as-element — nest with n/$, or bring interpreted hiccup across with h/as-element.

:rf.error/hicasso-native-intent-in-prop

You put an intent vector at a native callback slot, past the fence where nothing lowers it.

Recovery: :write-a-function-at-a-native-callback — a plain function; intents belong on the interpreted side of the fence.

:rf.error/hicasso-native-children-in-props

You wrote children in a native props map, which has one child channel.

Recovery: :pass-children-after-the-props-operand.

:rf.error/hicasso-native-slot-collision

You gave a native props map two source keys normalising to one React slot.

Recovery: :keep-one-spelling-per-react-slot.

:rf.error/hicasso-native-unknown-option

You gave an n/defcomponent declaration map a key outside its roster #{:server}:fallback, defhost's sibling option, being the one most often borrowed.

Recovery: :declare-the-server-policy.

:rf.error/hicasso-native-bad-server-policy

You gave an n/defcomponent declaration a :server value outside #{:client-only :render}.

Recovery: :declare-client-only-or-render.

Routing

A route link is an ordinary anchor that the router owns. These fire where it was asked to be something else.

Taught in Routing and navigation.

You rendered a route link with no ambient frame.

Recovery: :render-route-links-inside-a-boundary.

You gave a route link an :on-click outside the route-click roster.

Recovery: :veto-with-prevent-a-callback-or-nothing.

You wrote :prefetch on a route link (declined outright in v0).

Recovery: :spell-prefetch-as-an-on-mouse-enter-intent:on-mouse-enter [:rf.route/prefetch {…}] needs nothing the link does not already give you.

:rf.error/routing-artefact-missing

You rendered a route link with routing absent. This is a corpus id rather than a Hicasso one: the wider framework defines the spelling and Hicasso reuses it.

Recovery: :no-recovery — add day8/re-frame2-routing to your dependencies and require re-frame.routing at boot, before frames are constructed.

Named in Routing and navigation.

Motion and presence

A presence tray animates a child out after that child has stopped being rendered, which it can only do if it can still identify the child and knows when to give up.

Taught in Motion and presence.

:rf.error/hicasso-presence-child-not-hiccup

You gave a presence boundary a child that is not a hiccup vector.

Recovery: :give-every-presence-child-a-keyed-hiccup-vector.

:rf.error/hicasso-presence-child-unkeyed

You gave a presence child no :key.

Recovery: :put-a-key-in-the-child-props-map.

:rf.error/hicasso-presence-timeout-required

You left a presence boundary's timeout absent or not positive.

Recovery: :give-presence-a-positive-timeout-ms.

:rf.error/hicasso-presence-override-on-a-view

You wrote a phase-attribute override on a view head.

Recovery: :read-the-phase-prop-inside-the-view.

Named in Motion and presence.

:rf.error/hicasso-presence-override-out-of-reach

You wrote a phase-attribute override where no presence tray can apply it — deeper than a tray's direct child, forwarded through a :& remainder, or under no tray at all.

Recovery: :put-the-override-on-a-presence-child — move it onto the tray's own child; below a view head, branch on the :rf/phase prop instead.

Overlays and focus

An overlay positions itself against a trigger you name by DOM id, so the one thing it cannot do is resolve a name to nothing and say nothing.

Taught in Overlays and focus.

:rf.error/hicasso-overlay-anchor-missing

You gave an overlay an :anchor naming a DOM id no element in the document carries. Omitting :anchor is legal and silent — a modal takes none, and a popover without one is asking for the default position; this catches the name that resolves to nothing.

Recovery: :give-the-trigger-the-dom-id-the-anchor-names — 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

A concern is registered once and keyed by something the domain owns. Both halves are checked at registration and at use.

Taught in Ephemeral state.

:rf.error/hicasso-state-bad-concern

You registered an ephemeral-state concern that is not namespace-qualified.

Recovery: :namespace-qualify-the-concern.

:rf.error/hicasso-state-bad-key

You used an instance key outside the accepted set (nil included).

Recovery: :key-the-widget-by-a-domain-id.

:rf.error/hicasso-state-bad-option

You passed non-map options, or an option outside the roster, at registration.

Recovery: non-map arm :pass-a-map-of-options; unknown-key arm :remove-the-unknown-option.

:rf.error/hicasso-state-redefined

You re-registered a concern with a different default.

Recovery: :register-each-concern-once.

The test kit

L2 renders one body as a semantic tree with no React running. It refuses rather than guesses whenever the thing being asserted is not visible at that level, and the recovery is usually the next level up rather than a different assertion.

Taught in Testing.

:rf.error/hicasso-test-not-a-body

You gave an L2 tree form a head that is not a defview body.

Recovery: :pass-the-body-fn.

:rf.error/hicasso-test-not-a-render-form

You gave an L2 tree something other than a hiccup form.

Recovery: :pass-a-hiccup-form.

:rf.error/hicasso-test-plain-fn-head

You put a plain function in a hiccup head inside an L2 tree.

Recovery: :mint-the-boundary-or-render-it-as-the-root.

Named in Diagnostics.

:rf.error/hicasso-test-boundary-body-not-retained

You gave an L2 tree a minted head in a build that erased its body.

Recovery: :render-the-body-fn-or-mount-at-l3.

:rf.error/hicasso-test-bad-option

You gave an L2 tree non-map options, or an option outside its closed roster #{:subs}.

Recovery: non-map arm :pass-a-map-of-options; unknown-key arm :remove-the-unknown-option.

:rf.error/hicasso-test-bad-reads

You gave an L2 tree a :subs option that is not a query-to-value map.

Recovery: :pass-a-map-of-query-to-value.

:rf.error/hicasso-test-missing-read-fixture

You let an L2 body read a subscription no fixture answers.

Recovery: :add-the-query-to-subs.

:rf.error/hicasso-test-host-is-opaque

You let a defhost crossing reach the L2 semantic tree.

Recovery: :assert-it-at-l3.

:rf.error/hicasso-test-react-is-opaque

You let a raw React element reach the L2 semantic tree.

Recovery: :assert-it-at-l3.

:rf.error/hicasso-test-not-a-host

You read the declared server policy off something that is not a defhost.

Recovery: :pass-the-defhost-var.

:rf.error/hicasso-test-not-a-native-form

You gave an L1 projection a form whose head is not a tag keyword.

Recovery: :pass-a-native-hiccup-form.

:rf.error/hicasso-test-not-an-intent

You gave the L1 marker materializer something other than an intent vector.

Recovery: :pass-an-intent-vector.

:rf.error/hicasso-test-not-a-dom-node

You gave the canonical-DOM comparator something that is not a DOM node.

Recovery: :pass-a-dom-node.

:rf.error/hicasso-test-no-handler-at-position

You fired at a prop position the form does not write.

Recovery: :name-a-position-the-form-writes.

:rf.error/hicasso-test-position-is-not-a-handler

You fired at a position that lowers to something other than a function.

Recovery: :name-an-event-position.

:rf.error/hicasso-test-l1-dispatch

You invoked a handler lowered by a pure L1 projection.

Recovery: :assert-the-intent-as-data.

Raised at the substrate itself

One complaint is about Hicasso rather than about your code. If you see it, the useful report is the complaint it was trying to raise.

:rf.error/hicasso-refusal-incomplete

You minted a refusal missing one of the four required slots.

Recovery: :give-the-refusal-every-required-field.

Ids that are claimed but not raised

The index above is what the shipped package raises. A few further spellings are claimed without being raised, and knowing which is which saves a fruitless search: none of them can appear in an error you caught, and none of them is a spelling to mint for yourself.

Three rules govern the whole set, and they are the reason an id is worth asserting on in the first place.

  1. An id never changes meaning. If the refusal it names becomes a different refusal, that is a new id and the old one retires.
  2. An id never changes spelling. A rename is a retirement plus a mint, and both are recorded.
  3. A retired id is tombstoned and never reused. A consumer's stored errors, an error monitor's grouping rule and a page of prose all outlive the code, so a reused spelling makes every one of them silently wrong about which failure it saw.

Reserved

Each of these names a refusal this guide already teaches by mechanism, on a surface that is not built yet. The reservation is not bookkeeping: a refusal with no id is invisible to a round trip — nothing raises it and no index carries it, so the raise-set and the index agree while the coverage is entirely missing. Claiming the spelling makes that gap countable, stops two builders minting two names for one refusal, and lets a chapter cite an id today.

A reserved id carries no payload and no :recovery yet; those are settled by the work that writes the emitter, which moves the row up into the index above in the same change. So a reservation is promoted, never drifted into.

Reserved What it will refuse
:rf.error/hicasso-view-called-directly a defview invoked as a function instead of mounted as a hiccup head. The static case is caught already — the clj-kondo export Hicasso ships reports it as :re-frame.hicasso/direct-view-call, at error level — and this is the runtime half
:rf.error/hicasso-test-hook-is-opaque a React hook reached from a body run at L2, where no React is running
:rf.error/hicasso-test-native-is-opaque a native-tier element reaching the L2 semantic tree, as host and raw-React elements already do
:rf.error/hicasso-contenteditable-not-controllable a controlled :value binding on a contenteditable region
:rf.error/hicasso-route-link-bad-prefetch a route link's :prefetch carrying a value the link does not accept

Retiring later

:rf.error/hicasso-route-link-prefetch-declined is live today and already decided against. It retires when route links accept :prefetch rather than declining the key outright, and its successor is the reserved :rf.error/hicasso-route-link-bad-prefetch above.

The declined spelling is never reused for the successor, and the reason is worth seeing: the two say different things — this key does nothing here against this value is not one of the ones it takes — so an error monitor grouping by id would silently merge a v0 report with a later one about a typo.

Dead

:rf.error/hicasso-test-residue-after-quiescence is tombstoned. It was reserved for a raising clean-state assertion on the mounted test kit, and the kit landed choosing to report instead: hm/assert-clean! files residue through the test runner rather than throwing, because residue is a test failure and not a refusal of the instrument. A throw would make the tool that detects a leak indistinguishable from the tool breaking, and would abort at the first finding instead of reporting all of them. So the reservation named a refusal its own surface decided not to have. It was never minted and never raised, so no stored error and no monitor rule anywhere carries it — and by rule 3 above it stays dead rather than being recycled for the next refusal on that surface.