[]previous and next page

The smallest resource

useResource(fetcher) calls fetcher once when the component is created and exposes the result as reactive state.

For agentsUpdated

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.