The smallest resource
useResource(fetcher) calls fetcher once when the component is created and exposes the result as reactive state.
useResource(fetcher) calls fetcher once when the component is created and exposes the result as
reactive state.
src/user-card.tsxtsx
import { useResource } from "@shadow-js/core";
import { Show } from "@shadow-js/web";
export interface User {
readonly id: number;
readonly name: string;
}
export function UserCard(props: { load: () => Promise<User> }) {
const user = useResource(() => props.load());
return (
<section>
<Show when={user.loading}>
<p>loading</p>
</Show>
<Show when={user.error !== undefined}>
<p role="alert">failed</p>
</Show>
<h2>{user.latest?.name}</h2>
</section>
);
}
What a resource gives you#
A Resource<T> is an accessor (user()) with these members:
| Member | Meaning |
|---|---|
state |
"unresolved", "pending", "ready", "refreshing" or "errored" |
loading |
true while state is "pending" or "refreshing" |
error |
the thrown or rejected value; undefined otherwise |
latest |
the last good value; never throws and never suspends |
user() |
the value, with side effects: throws the error when errored, and inside a <Suspense> holds the boundary in its fallback while the first load is pending |
refetch() |
fetches again keeping the old value visible; returns a promise of the new value (undefined if it failed or a newer fetch replaced it); never rejects |
mutate(value | updater) |
replaces the value without fetching, cancels a fetch in flight, returns the new value |
Read latest, loading and error when you want to render every state yourself (as above). Call
user() when you want boundaries to do it: <Suspense> shows its
fallback until the first value arrives, and an
<ErrorBoundary> catches the thrown error.
src/boundaries.tsxtsx
import { useResource } from "@shadow-js/core";
import { ErrorBoundary, Suspense } from "@shadow-js/web";
function Name(props: { load: () => Promise<string> }) {
const name = useResource(() => props.load());
return <strong>{name()}</strong>;
}
export function Profile(props: { load: () => Promise<string> }) {
return (
<ErrorBoundary fallback={(error) => <p role="alert">{String(error)}</p>}>
<Suspense fallback={<em>loading</em>}>
<Name load={props.load} />
</Suspense>
</ErrorBoundary>
);
}
A refetch never suspends again: after the first value exists, a refetch moves state to
"refreshing" and the old value stays on screen until the new one replaces it.