Events¶
The introduction closed the loop once. This page is the language of that loop: what events look like, how you announce them, and what handlers are allowed to do.
Nothing moves without an event. Design the event set and you design the app.
Event shape¶
An event is a vector. The first element is the id — almost always a namespaced keyword. Further elements are optional facts; the common case is one payload map:
[:inc]
[:cart/add {:sku "A" :qty 1}]
[:article/loaded {:id 42 :title "…"}]
[:route/changed {:page :about :params {}}]
Ids are the vocabulary of the application. Prefer names that say what happened
(:cart/item-added) or what was intended (:cart/add), not how a view is
implemented.
There is no second channel. Timers, HTTP replies, route loaders, and button clicks all speak this shape. That uniformity is why tools can show one timeline for the whole app.
Dispatch¶
You announce an event with dispatch:
Inside a reg-view, dispatch is injected for you (same for subscribe):
Dispatch does not run the handler. It enqueues the event on the frame's FIFO queue and returns immediately. The runtime dequeues later and runs the full pipeline for that event. That separation keeps UI handlers thin and keeps the write path single-threaded.
Need the pipeline to finish before your next line of code? Reach for
dispatch-sync only when you must (boot, some interop). Prefer ordinary
dispatch in views. (The full drain story lives with
Effects.)
No ambient frame
dispatch must know which frame owns the queue. Inside a
view under frame-root / frame-provider, that is automatic. From a bare
setTimeout or a foreign callback with no frame in scope, bare dispatch
raises :rf.error/no-frame-context. Carry a frame (or a capture-frame
bundle) out of the tree — Frames covers the pattern.
Handlers return descriptions¶
Register a handler with reg-event. The handler receives the world (coeffects
map — at minimum {:db current-app-db}) and the event vector, and returns an
effect map:
Rules that matter from day one:
- Pure. Same inputs, same returned map. No
js/fetch, noswap!, no reading the clock. Impurity is described and performed later (Effects; recorded inputs are Coeffects). :dbis the next app-db value, not a patch instruction. Useassoc,update,update-in— functions that return a new map.- The runtime commits. Your function proposes; the pipeline applies.
A handler may return other effect keys (:fx, …) alongside or instead of :db.
It may return no :db and leave state alone. The state doctrine lives on
app-db; the to-do list beyond :db lives on Effects.
Metadata when you need it¶
reg-event accepts an optional metadata map between the id and the function —
schemas, required coeffects, interceptors. Until you need that, the two-argument
form is enough. The first useful metadata form is on
Coeffects.
What events are not¶
| Not this | Why |
|---|---|
| A place to put view logic | Views stay pure; they dispatch and subscribe |
| A free-form message bus between components | Components don't address each other — they write app-db through events |
Something you await |
Async replies arrive as later events (Effects, Async) |