variable / @typed/template/Render

render

Mounts any Renderable to a specific DOM element.

This function lifts primitives, arrays, Effects, Streams, and Fx values into a render stream and keeps the target DOM element updated. It handles:

  • Mounting the initial content.
  • Updating the content as new events are emitted.
  • Hydrating the content if hydration context is provided.
Package version
1.0.0-beta.13
Category
DOM mounting
Since
1.0.0

Import

import { render } from "@typed/template/Render";

Signatures

export declare const render: {
    (where: HTMLElement): <const T extends Renderable.Any>(renderable: T) => Fx.Fx<ToRenderedRenderable<T>, Renderable.Error<T>, Renderable.Services<T>>;
    <const T extends Renderable.Any>(renderable: T, where: HTMLElement): Fx.Fx<ToRenderedRenderable<T>, Renderable.Error<T>, Renderable.Services<T>>;
};

Why

render makes the root ownership boundary explicit. It connects any Renderable to one concrete element while preserving the Renderable’s typed failures and Effect service requirements. The source can be a primitive, template, Effect, Stream, Fx, or existing RenderEvent.

Ownership and lifetime

Calling render returns an Fx and starts no work. The Scope that runs that Fx owns dynamic subscriptions and cleanup. The renderer tracks only the rendered value associated with where; it does not claim the document, overwrite unrelated class names, or remove nodes outside its dynamic ownership.

DOM updates and hydration

Scalar template parts retain direct targets and update without traversing a virtual tree. Structural output reconciles only its bounded range. Existing server-rendered nodes can be hydrated through HydrateContext instead of replaced, preserving their identity and native browser state.

Examples

import { Effect, Layer } from "effect"
import { html } from "@typed/template"
import { DomRenderTemplate, render } from "@typed/template/Render"
import { Fx } from "@typed/fx"
import { RefSubject } from "@typed/fx/RefSubject"

const program = Effect.gen(function* () {
  const count = yield* RefSubject.make(0)

  const template = html`<div>
    <p>Count: ${count}</p>
    <button onclick=${RefSubject.increment(count)}>Increment</button>
  </div>`

  // Render to document.body
  return yield* render(template, document.body).pipe(
    Fx.drainLayer,
    Layer.provide(DomRenderTemplate),
    Layer.launch
  )
})

// Can also use pipe syntax
const program2 = Effect.gen(function* () {
  const template = html`<div>Hello</div>`

  yield* template.pipe(
    render(document.body),
    Fx.drainLayer,
    Layer.provide(DomRenderTemplate),
    Layer.launch
  )
})

Other public imports

These import paths expose the same declaration. Each page retains its own public name and signature.

Related reading

Source