Rendering
flexium/dom provides the renderer + a few declarative primitives for async, error, and portal patterns.
render()
Mount a component tree into a DOM container:
import { render } from 'flexium/dom'
render(<App />, document.getElementById('app')!)render returns a dispose function — call it to unmount:
const dispose = render(<App />, container)
// later...
dispose()Suspense
Pause render at a boundary while async work completes:
import { Suspense } from 'flexium/dom'
function Page() {
return (
<Suspense fallback={<p>Loading user…</p>}>
<UserProfile id={1} />
</Suspense>
)
}
function UserProfile({ id }) {
// Returns a Promise — Suspense pauses until resolved
const [user] = use(async () => fetch(`/api/users/${id}`).then(r => r.json()))
return <h1>{user.name}</h1>
}While the promise is pending, the fallback is rendered. When the promise resolves, the component re-renders with the value.
Nest Suspense boundaries to control loading granularity:
<Suspense fallback={<Spinner />}>
<Header />
<Suspense fallback={<RowSkeleton />}>
<DataTable />
</Suspense>
</Suspense>If Header is sync, it shows immediately. The DataTable shows its own skeleton until its data resolves.
ErrorBoundary
Catch rendering errors and show a fallback:
import { ErrorBoundary } from 'flexium/dom'
<ErrorBoundary fallback={(err) => <p style="color:red">Failed: {err.message}</p>}>
<RiskyComponent />
</ErrorBoundary>With a render-prop fallback you can offer recovery:
<ErrorBoundary fallback={(err, reset) => (
<div>
<p>{err.message}</p>
<button onclick={reset}>Try again</button>
</div>
)}>
<RiskyComponent />
</ErrorBoundary>Calling reset re-mounts the children, giving the component a fresh attempt.
Portal
Render content into a different DOM subtree (modals, tooltips, toasts):
import { Portal } from 'flexium/dom'
function Modal({ children }) {
return (
<Portal target={document.body}>
<div class="modal-overlay">
<div class="modal-content">{children}</div>
</div>
</Portal>
)
}The child JSX renders into document.body (or any target element) but stays logically inside its parent — context, signals, and event bubbling work normally.
lazy()
Code-split a component:
import { lazy } from 'flexium/dom'
const HeavyChart = lazy(() => import('./HeavyChart'))
<Suspense fallback={<Spinner />}>
<HeavyChart />
</Suspense>lazy(fn) returns a component that triggers fn() on first render. The dynamic import becomes a separate chunk; Suspense handles the loading.
Hydration (SSR)
For server-rendered HTML, hydrate() attaches event handlers to the existing DOM:
import { hydrate } from 'flexium/dom'
hydrate(<App />, document.getElementById('app')!)The server-rendered markup is preserved; signals attach to the existing nodes and updates flow normally from that point. See Server-Side Rendering.
render() vs hydrate()
render() | hydrate() | |
|---|---|---|
| Use case | Pure client app (CSR) | Existing HTML from server (SSR) |
| Behavior | Discards container, mounts fresh | Attaches to existing DOM |
| FCP | After JS download + parse | Before JS — HTML loads first |
Next
→ Routing — Routes, Link, useRouter.