Await

Fetching data from a server is asynchronous, so the UI may need to display a loading state while the request is pending.

<Await> provides a boundary for this state. It hides the boundary's content and displays its fallback until the asynchronous data used within the boundary has finished loading.

Async Cells

<Await> works with Async Cells. A normal Cell holds a value. An Async Cell, created using Cell.derivedAsync(), holds a Promise that resolves to a value.

When an async cell is used in a template, Retend detects its pending state and notifies the nearest <Await> ancestor.

Basic Usage

To use an asynchronous boundary, wrap your components in <Await> and provide a fallback element to show while loading.

import { Await, If, Cell } from 'retend';

function UserProfile() {
  // 1. Create an Async Cell to fetch data
  const userData = Cell.derivedAsync(async () => {
    const response = await fetch('https://api.example.com/user');
    return response.json();
  });

  return (
    <div class="user-profile">
      {/* 2. Because userData is an Async Cell, this If() automatically
             tells the parent <Await> to show the fallback until the fetch finishes. */}
      {If(userData, {
        true: (user) => (
          <div>
            <h2>{user.name}</h2>
            <p>{user.email}</p>
          </div>
        ),
      })}
    </div>
  );
}

export function App() {
  return (
    <main>
      <h1>Dashboard</h1>

      {/* 3. Wrap everything in an <Await> boundary */}
      <Await fallback={<p>Loading user data...</p>}>
        <UserProfile />
      </Await>
    </main>
  );
}

While the request is pending, <Await> displays Loading user data.... After the request resolves, it removes the fallback and renders UserProfile with the user's name and email.

Waiting for Multiple Things

An <Await> boundary waits for all asynchronous operations inside it to finish before rendering its content.

If DashboardContent fetches the user's profile and a list of recent posts, the <Await> fallback remains visible until both requests finish.

import { Await, If, For, Cell } from 'retend';

function DashboardContent() {
  const userData = Cell.derivedAsync(async () =>
    fetch('/api/user').then((r) => r.json())
  );
  const postsData = Cell.derivedAsync(async () =>
    fetch('/api/posts').then((r) => r.json())
  );

  return (
    <div>
      {/* Retend sees both of these Async Cells and tells the boundary to wait for both! */}
      {If(userData, (u) => (
        <h2>{u.name}'s Dashboard</h2>
      ))}
      {For(postsData, (p) => (
        <p>{p.title}</p>
      ))}
    </div>
  );
}

export function Dashboard() {
  return (
    // This fallback shows until both userData AND postsData are done
    <Await fallback={<p>Loading dashboard entirely...</p>}>
      <DashboardContent />
    </Await>
  );
}

To load them independently, wrap the two sections in separate <Await> components.

Server-Side Rendering (Advanced)

For Server-Side Rendering (SSR), <Await> boundaries identify asynchronous work that must resolve before the server sends the HTML response.

Use waitForAsyncBoundaries() in server code to wait for these boundaries to settle.