Loading
Renders a fallback while pending async reads inside the subtree settle.
Any computation (createMemo, createSignal(fn), createStore(fn),
lazy(...), etc.) that throws because data isn't ready is caught by the
nearest enclosing <Loading>. The boundary swaps to its fallback until
every pending read has resolved, then renders the children.
Once content has rendered, a refetch keeps it visible: the boundary reads
the pending value like any other reader and holds the write that made it
pending until the data lands (isPending() flips meanwhile).
The optional on prop is a dependency list. The expression is tracked and
its value is irrelevant — what matters is what it reads. Whenever anything
it reads changes (a plain write, an optimistic write, a source going
pending or landing), the boundary stops waiting on its current content and
shows fallback again if something under it is still pending, until the
new content is ready; if nothing is pending, nothing happens. The fallback
lands with the same frame as the change that caused it: immediately when
nothing else holds that frame; together with the rest of the new page
during a held navigation (a write inside an action, or one whose data
other readers are still waiting on) — never a spinner beside a page the
change has not reached yet. If the same data is also read outside the
boundary, the frame waits on it and the fallback can never be seen; DEV
warns LOADING_ON_OUTSIDE_HOLD, and the fix is structural — move the
outside read under the boundary so one hold owns the data. A frame held
past the content's landing by something else (the write's action, other
pending data) shows no fallback either; that is a race, a legitimate
outcome, and not reported — show the wait with isPending() instead.
A display-ahead read in on (latest()) shows the fallback now, beside
the held frame; that is a capability, not the recommended shape. The
children are not re-created; they stay alive behind the fallback.
Scope <Loading> around the data-dependent slot, not the surrounding
shell. Wrapping layout chrome (header, nav, footer) in the same boundary
as the data means revalidation replaces the entire screen with the
fallback; rendering chrome outside the boundary keeps it stable while
only the affordance flips.
Import
import { Loading } from "solid-js";Type signature
function Loading(props: { fallback?: JSX.Element; on?: any; children: JSX.Element;}): JSX.Element;Props
fallback
- Type:
JSX.Element - Optional
Rendered while an async read inside the subtree has no settled value.
on
- Type:
any - Optional
A value, not an accessor. When it changes and the new content is not ready, the boundary shows its fallback again instead of holding the update.
children
- Type:
JSX.Element
The subtree whose async reads this boundary handles.
Examples
const Profile = lazy(() => import("./Profile"));
<Loading fallback={<Spinner />}> <Profile /></Loading>;// Depend on the route: a navigation shows the skeleton with the new route// while the page loads; a refetch of the same route keeps the page visible.<Loading fallback={<Skeleton />} on={route()}> <Page /></Loading>
// Several dependencies: a change to any of them shows the fallback.<Loading fallback={<Skeleton />} on={[query(), page()]}> <Results /></Loading>Caveats
- The boundary shows its fallback only when a read inside has no settled value yet. Later updates hold the previous content; use
onfor content that should show a placeholder again. oncompares values, not accessors. Passon={id()}, noton={id}.- Place the boundary around the data-dependent slot, not around layout chrome that should stay stable.