Skip to content

Rendering Boundaries

Observable state only helps rendering if reads happen inside a reactive boundary. use-legend gives you explicit boundaries and a transform that can create common boundaries automatically.

See Auto-Tracking & .get() for how JSX-positioned .get() reads become reactive leaves.

Memo tracks observable reads inside its function child:

import { Memo } from "@usels/core";
<Memo>{() => count$.get()}</Memo>;

With the Vite or Babel plugin, simple JSX reads are wrapped for you:

<span>{count$.get()}</span>

The transform emits a Memo boundary for that read so the parent component does not become the default update unit.

If a dense group of .get() reads would create too many tiny auto-wrapped boundaries, wrap the group in a manual Memo:

import { Memo } from "@usels/core";
<Memo>
<span>{cart$.total.get()}</span>
<span>{cart$.tax.get()}</span>
<span>{cart$.shipping.get()}</span>
</Memo>;

A user-authored Memo is treated as an explicit render boundary. The transform does not insert additional auto-generated Memo boundaries inside it.

Use this only when the reads really belong to one UI update. For independent rows or fields, prefer For or ordinary leaf reads.

Show is a conditional rendering boundary. It only re-renders its children when the condition observable changes — the parent component stays untouched:

import { Show } from "@usels/core";
<Show if={isLoggedIn$}>
<Dashboard />
</Show>;

Use else for the falsy branch:

<Show if={isLoading$} else={<Content data$={data$} />}>
<p>Loading…</p>
</Show>;

Prefer Show over inline conditionals like {obs$.get() && <JSX>} — the inline form re-renders the parent component on every change.

Computed creates a reactive boundary around arbitrary JSX. It re-renders only itself when any observable read inside changes — the parent component is not affected:

import { Computed } from "@usels/core";
<Computed>
{() => (
<span>
{firstName$.get()} {lastName$.get()}
</span>
)}
</Computed>;

Computed tracks any observable state change, not just derived values. With the transform enabled, most leaf reads are auto-wrapped — use Computed when you need an explicit reactive boundary around a block of JSX.

Use For for observable arrays. Each item gets its own boundary so individual rows update independently:

import { For } from "@usels/core";
<For each={items$}>{(item$) => <li>{item$.name.get()}</li>}</For>;

Avoid .get().map() — it re-renders the entire list when any item changes.

ScenarioBoundary
Ordinary leaf reads in JSXAuto-wrapped by the transform
Group of related reads that update togetherMemo (manual)
Conditional renderingShow
Explicit reactive block of JSXComputed
Observable array iterationFor