Function: renderSpec()
API / @xmachines/play-dom / renderSpec
function renderSpec( spec, store, registry, send, handlers, fallback?, onRenderError?, functions?, loading?, onWatchSetup?, validationFunctions?, navigate?,): Node | null;Defined in: packages/play-dom/src/json-render/renderer.ts:135
Render a Spec tree into DOM nodes using the provided DomRegistry.
Traverses the spec from spec.root, evaluates visibility, resolves all prop
expressions (including $state, $bindState, $computed, $template, etc.),
and calls the matching DomComponentRenderer for each visible element.
Event wiring and child rendering are handled inside the component wrapper
built by defineRegistry — this function only orchestrates the traversal.
Parameters
| Parameter | Type | Description |
|---|---|---|
spec | Spec | The json-render Spec describing the UI tree. |
store | StateStore | Live StateStore bound to spec.state. |
registry | DomRegistry | Map of element type names → DomComponentRenderer. |
send | (event) => void | Dispatcher for interaction events (e.g. actor.send). |
handlers | Record<string, ActionHandler> | Map of catalog action names → async ActionHandler functions. |
fallback? | DomComponentRenderer | Optional renderer called when registry has no entry for an element’s type (GAP-04). If absent, unknown types return null. |
onRenderError? | RenderErrorHandler | Optional callback matching RenderErrorHandler = (error, name). Invoked for three distinct error classes: - (error, elementType) when a component renderer throws (GAP-08) - (error, actionName) when an action handler rejects during emit() - (error, actionName) when an action handler rejects in a watch binding If absent, all three fall back to console.error. Forwarded into DomRenderContext.onRenderError so emit() and watch handlers route errors through the same channel as component render errors. |
functions? | Record<string, ComputedFunction> | Optional map of named compute functions forwarded to PropResolutionContext (GAP-03). Enables { $computed: "fn", args } prop expressions to resolve. When absent, $computed expressions return undefined (backward-compatible, no throw). |
loading? | boolean | Optional flag indicating the spec is still streaming (GAP-06). When true, components can read ctx.loading to render skeleton states, and missing-child warnings (GAP-07) are suppressed since referenced elements may not yet have arrived in the incremental spec. |
onWatchSetup? | (cleanup) => void | Optional callback invoked with each watch subscription cleanup function (GAP-02). When an element declares watch, the renderer sets up a store subscription and passes its unsubscribe function to this callback. Callers (e.g. PlayRenderer) collect these to call on disconnect(). |
validationFunctions? | Record<string, (value, args?) => boolean> | Optional map of custom validation functions forwarded to DomRenderContext.validationFunctions. Components pass these as customFunctions to runValidationCheck / runValidation from @json-render/core. Has no effect on prop resolution. |
navigate? | (path) => void | Optional navigation callback forwarded to DomRenderContext.navigate. Invoked automatically by defineRegistry’s emit() when an action binding resolves with onSuccess: { navigate: "/path" }. Also readable by component implementations via ctx.navigate. |
Returns
Node | null
The root DOM Node, or null if the root is invisible
or has no registered renderer.
Remarks
Devtools isolation in tests: The module-level devtoolsActive flag is a
singleton that persists for the lifetime of the module. In test environments,
any call to markDevtoolsActive() MUST be paired with a release() call
inside a try/finally block. Failing to do so leaves devtoolsActive = true
for all subsequent tests, causing false positives in devtools-conditional
rendering paths (e.g. data-jr-key wrapper spans).
const release = markDevtoolsActive();try { // test code} finally { release();}Example
const { registry, handlers } = defineRegistry(catalog, { components, actions });const resolvedHandlers = handlers( () => undefined, () => ({}),);const node = renderSpec(spec, store, registry, actor.send.bind(actor), resolvedHandlers);if (node) container.appendChild(node);