Browse recipes

Show a pending content boundary

Give one profile screen a visible asynchronous lifecycle.

Table of contents

APIs used

mount
Starts a Valyrian.js application on a DOM target.
Suspense
Renders fallback while asynchronous children resolve, then renders the result or error output.

Run this recipe

Starting project
Use the local TSX and ESM project from Stage 1 with Valyrian.js installed and its ESM browser build configured.
File
Replace src/client-entry.tsx with this recipe snippet.
Run
Run npm run build, serve public with npx --yes serve@14.2.5 public --listen 8000, open http://localhost:8000 and use the example's visible controls.
Observable result
Load profile shows fallback before success. Load rejected profile shows the same fallback before the error renderer.

Starting point

Wrap profile content in a pending boundary with an explicit fallback.

Steps

Give Suspense a new suspenseKey for each action, provide fallback and error renderers, and pass the delayed asynchronous profile as its child.

tsx
import { mount } from "valyrian.js";
import { Suspense } from "valyrian.js/suspense";

type Outcome = "success" | "error";

const state = { outcome: "success" as Outcome, run: 0 };

const wait = (milliseconds: number) =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

async function Profile({ outcome }: { outcome: Outcome }) {
  await wait(700);
  if (outcome === "error") {
    throw new Error("Profile request failed");
  }
  return <p>Loaded profile</p>;
}

function load(outcome: Outcome) {
  state.outcome = outcome;
  state.run += 1;
}

const Screen = () => {
  const suspenseKey = `profile:${state.outcome}:${state.run}`;
  return (
    <main>
      <button onclick={() => load("success")}>Load profile</button>
      <button onclick={() => load("error")}>Load rejected profile</button>
      <Suspense
        suspenseKey={suspenseKey}
        fallback={<p>Loading profile...</p>}
        error={(error) => <p>{String(error)}</p>}
      >
        <Profile outcome={state.outcome} />
      </Suspense>
    </main>
  );
};

mount("body", Screen);

Result

Load profile shows fallback before success. Load rejected profile shows the same fallback before the error renderer.

Limits

Each action changes suspenseKey and starts a fresh asynchronous run.

Continue