Troubleshooting & FAQ.
The questions developers hit most often, with the cause and the fix in one place. Most surprising behaviour comes from Aktion’s streaming-first defaults — once you know the rule, the fix is short.
Find your symptom
- Input loses focus while typing
- Two-way binding isn’t updating
- An effect isn’t re-running
- A component didn’t update
- A list reorders or loses state
- My
$httpdata never appears Map(…)rendered the wrong thing- An inline style was dropped
- My theme isn’t applying
- A translation shows the key
- Nothing rendered, no error
- “Reactive write during render” warning
Rendering & reactivity
My Input loses focus on every keystroke / state change
Cause. The morph reconciler preserves focus, selection,
and input values across re-renders — but a subtree rendered through
Portal is re-created rather than morphed, so a focused field
inside a Portal loses focus whenever an unrelated state change re-renders
the tree.
Fix. Keep focused, editable content out of a
Portal. For overlays that must hold a form, prefer
Modal (which traps and restores focus). If you must keep an
input mounted, bind it to a top-level atom so typing only triggers a
path-scoped update rather than a full re-render of the surrounding tree.
Typing in an Input doesn’t update my state (two-way binding)
Cause. A control only writes back when you pass the
$variable itself as its bound argument. Passing
$name’s value (e.g. interpolated into a
string) gives the control a one-way snapshot — it renders, but has
nothing to write to.
// ❌ no binding — the field shows the value but typing goes nowhere
Input({ placeholder: "Name", value: `${$name}` })
// ✅ pass the $variable last so the control reads AND writes it
$name = ""
Input("Name", { value: $name }, $name)
Fix. Pass the bare $variable as
the last argument to Input, Select,
Checkbox, Switch, Slider,
MultiSelect, and the other bindable controls. See
Components for each control’s binding
argument.
My effect isn’t re-running
Cause. $effect(fn, [deps]) only re-runs when
one of its tracked dependencies changes: reactive atoms /
$store fields read in the dependency array, plus the
component lifecycle and timer ticks. A value that isn’t a reactive
read (a plain local, a prop captured by value) won’t re-trigger the
effect when it changes.
// ❌ depends on a plain local — never re-fires when $userId changes
$effect(() => load(id), [])
// ✅ list the reactive atom; the effect re-runs when $userId changes
$effect(() => load($userId), [$userId])
Fix. Put the actual reactive atom(s) the
effect depends on into the dependency array. If you need to react to a
derived value, derive it from atoms (or a $memo) so the
dependency is itself reactive. An empty array [] means
“run once on mount”.
My component didn’t update — it was “memoized away”
Cause. On a fine-grained update, a PascalCase component whose props are unchanged is skipped and its previous DOM is reused. If the component reads state it didn’t receive as a prop and that state changes via a path the component never subscribed to, it can be skipped.
Fix. Read the reactive value inside the component (so it subscribes to the path), or pass it in as a prop so a changed value breaks prop equality and forces a re-render. Use DevTools’ “why did this render” to confirm whether an instance was skipped.
Data, lists & async
My list reorders, animates wrong, or loses per-row input state
Cause. Without a stable key, the morph
reconciler matches list children by position. When the array
reorders, inserts, or filters, DOM nodes (and their focus / input
values) get reused for the wrong row.
// ❌ positional matching — inserting at the top shifts every row's DOM
$items.map(item => Card([Text(item.title)]))
// ✅ key by a stable id so identity follows the data
$items.map(item => Card([Text(item.title)], { key: item.id }))
Fix. Give every item in a mapped list a
key tied to stable data identity (an id, not the array
index). This also lets the memoizer skip unchanged rows — see
Performance.
My $http data never appears (blank where the data should be)
Cause. An $http({...}) call returns a
resource with a lifecycle (loading →
data / error), not the data directly. Rendering
the resource object itself shows nothing useful while it’s still
loading.
$users = $http({ url: "/api/users" })
// ✅ switch on the resource state with Async
Async($users, {
loading: Spinner(),
error: Callout("Couldn't load users", { tone: "danger" }),
empty: EmptyState("No users yet"),
data: List($users.data.map(u => ListItem(u.name))),
})
Fix. Render the resource through
Async(resource, { loading, error, empty, data }) (or read
resource.data / resource.state yourself). See
HTTP.
Common gotchas
Map(...) rendered something unexpected (not a JS Map)
Cause. Map is a built-in component
name in Aktion (the geographic map). A bare Map(…) in
render position resolves to that component, not the JavaScript
Map constructor.
Fix. For a JS hash map use an object literal
({}) or the array/object helpers in $util
($util.keyBy, $util.groupBy). Reserve
Map(…) for the map component.
My inline style was dropped
Cause. Styles pass through a sanitizer. Declarations that
look unsafe (e.g. url(javascript:…), expression-based
values) are dropped silently so streamed, partial output can never inject
script.
Fix. Use plain, static CSS values. For anything dynamic, compute the value in an expression and pass a clean string. Turn on strict mode to get a console warning when a declaration is rejected instead of a silent drop.
My $theme({...}) override isn’t applying
Cause. Two things commonly bite: (1) the in-program
$theme({...}) form takes grouped keys
(colors.primary), while the host theme attribute
and setTheme() take flat keys
(colorPrimary); and (2) unknown token names are silently
ignored as a typo guard, so a misspelled token does nothing.
// ✅ in-program: grouped keys
$theme({ colors: { primary: "#e11d48" }, radius: { md: "14px" } })
// ✅ host attribute: flat keys (JSON)
// <aktion-app theme='{"colorPrimary":"#e11d48"}'></aktion-app>
Fix. Match the key shape to where you set the theme, and check the token name against the Themes token reference.
A missing translation shows the key instead of text
Cause. When an $i18n key has no entry for the
active locale, the runtime returns the key itself rather than throwing —
so a partially-translated app still renders.
Fix. Add the missing key to the locale table. In strict mode, missing keys are surfaced as console warnings so you can find them during development.
Silent failures & warnings
Nothing rendered and there was no error
Cause. By design, unknown identifiers evaluate to
null, non-callable callees return null, and an
unknown component renders a Skeleton — so mid-stream
partial output never crashes.
Fix. Enable strict mode
(strict attribute on <aktion-app>) to turn
these silent fallbacks into console warnings. See
Error handling & debugging.
I got a “reactive write during render” warning
Cause. A $name = … assignment ran in
render position (commonly a $x = … at the top of a
lowercase function that is invoked to build UI). The runtime applies the
write without scheduling a re-render to avoid an infinite loop, and warns.
Fix. Seed component-local state with a
PascalCase component (so $x = … becomes a
set-once per-instance declaration) or the $state hook, and
only write state from event handlers / effects. See
Reactivity & rendering.
Still stuck?
Two switches surface almost everything: add the strict
attribute to <aktion-app> to turn silent fallbacks into
console warnings (see Error handling), and open
the DevTools panel to inspect live state and
read each commit’s “why did this render” reason.
Next
Error handling & debugging
Reading parse/runtime errors, the guards, the error event, and strict mode.
Reactivity & rendering
Why re-renders happen and how to keep them fine-grained.
Read the guide → AdvancedDevTools
Diagnose unexpected re-renders with the commit profiler.
Open the guide →