[]previous and next page

Fetching on input: the source form

useResource(source, fetcher) runs fetcher(value, info) for the current value of source() and again whenever it changes.

For agentsUpdated

useResource(source, fetcher) runs fetcher(value, info) for the current value of source() and again whenever it changes. While the source is false, null or undefined the resource does not fetch (it is "unresolved", or "ready" if it already had a value). A plain, non-function source fetches once for that value.

info is { signal, refetching, value }:

  • signal is an AbortSignal that aborts when a newer fetch starts, when the source becomes empty, or when the owner is disposed. Pass it to fetch so the network request is canceled, not just ignored.
  • refetching is true for refetch() and for source changes after a value existed.
  • value is the last value, for fetchers that merge (pagination, patches).

A response that arrives after a newer fetch started is dropped, whether or not the fetcher honors the signal, so an out-of-order response can never overwrite a newer one.

src/search.tsxtsx
import { useResource, useStore } from "@shadow-js/core";
import { Show } from "@shadow-js/web";

export interface Backend {
  find(term: string, signal: AbortSignal): Promise<string[]>;
}

export function Search(props: { backend: Backend }) {
  const [term, setTerm] = useStore("");
  // An empty term is falsy, so nothing is fetched for it.
  const results = useResource(
    () => term().trim() || false,
    (value, { signal }) => props.backend.find(value, signal),
  );
  return (
    <div>
      <input aria-label="term" value={term()} onInput={(event) => setTerm(event.currentTarget.value)} />
      <Show when={results.loading}>
        <p>searching</p>
      </Show>
      <ul>{(results.latest ?? []).map((row) => <li>{row}</li>)}</ul>
    </div>
  );
}

(flush comes from @shadow-js/testing, which @shadow-js/testing/dom re-exports.)