@zoijs/resource

The simplest way to read async data. A resource wraps a fetcher and gives you reactive loading, data, and error states.

npm install @zoijs/core @zoijs/resource

Example#

import { html, mount } from "@zoijs/core";
import { resource } from "@zoijs/resource";

function Profile() {
  const user = resource(() => fetch("/api/user").then((r) => r.json()));

  return html`
    ${() =>
      user.loading() ? html`<p>Loading…</p>`
      : user.error() ? html`<p>Couldn't load.</p>`
      : html`<h1>${user.data().name}</h1>`}
  `;
}

mount(Profile, "#app");

It runs the fetcher once on creation. The three readers are reactive — read them inside a binding and the DOM updates when the data arrives.

API#

MemberWhat it gives you
resource(fetcher)Create a resource; loads immediately
data()The loaded value, or undefined before the first success
loading()true while a load is in flight
error()The error, or null
refresh()Load again (keeps old data until the new load resolves)
Pairs with @zoijs/action for writes: resource reads, action writes.

Error monitoring#

A failure that becomes error() is also reported to the app's configure({ onError }) hook from @zoijs/core, as { kind: "resource" } with the original error (superseded or post-unmount results aren't reported). It goes through the core's shared runtime, so a no-build import map needs no extra entry. Reports need @zoijs/core 1.9.0 or later — older cores have no shared runtime, so nothing is reported (and nothing breaks).

import { configure } from "@zoijs/core";
configure({ onError: (error, info) => reportToMonitoring(error, info.kind) });

Known limitations#

  • No cache, request dedupe, or retries — each resource(...) is its own state.
  • SSR hand-off: pass { initial } to start settled with server data — serialize it with @zoijs/ssr's serialize and the resource keeps it on hydration instead of refetching. Per-request data loaders aren't included.