smolter
API

Await

Render deferred values with component-owned loading and error content.

Compatibility

React · AlternativeReact 19+ provides use(promise) with Suspense and an error boundary. Use a stable promise. Smolter's React application integration revives streamed loader promises before hydration. React use + Suspense
React SSR · AlternativeReact 19+ provides use(promise) with Suspense and an error boundary. Use a stable promise. Smolter's React application integration revives streamed loader promises before hydration. React use + Suspense
PreactAvailable in this renderer's browser integration.
Preact SSRAvailable to this renderer's SSR integration; browser event handlers run after hydration.

Import from @kasperrt/smolter/preact

Signature

function Await<T>({
  resolve,
  ...props
}: AwaitProps<T>): ComponentChildren;

Parameters

resolve accepts a value or Promise. children receives Awaited<T>. fallback supplies component-owned pending content; pass null to render nothing. errorElement is optional and may be null on rejection.

Pending and resolved values

For early hydration, put fallback directly on Await. The server renders ordinary fallback HTML; the existing deferred-data stream delivers the result, and the mounted component updates its own content. No streamed Suspense DOM is inserted. Preserve the promise identity between renders.

import { Await } from "@kasperrt/smolter/preact";

export function Reviews({ reviews }: { reviews: Promise<string[]> }) {
  return (
    <Await resolve={reviews}
      fallback={<p role="status">Loading reviews…</p>}
      errorElement={<p>Reviews are unavailable.</p>}>
      {(items) => <ul>{items.map((item) => <li key={item}>{item}</li>)}</ul>}
    </Await>
  );
}

Rejections

When errorElement is supplied, Await renders it instead of children. Otherwise the rejection is thrown during rendering and must be handled by a Preact error boundary. useRouteError reads router request errors; it does not expose this locally handled promise rejection. Failures detected after initial Smolter hydration starts preserve the supplied error element’s role, ARIA attributes and styles; existing server-rendered error content hydrates in place. Use an appropriate alert or status message for information the user needs to hear.

Server rendering

Await starts no requests. With earlyHydration enabled, an explicit fallback is rendered and hydrated normally; promise settlements update only mounted components. Deferred results are sent as data, not resolved HTML, so JavaScript is required to replace these fallbacks. Ordinary SSR without early hydration still streams resolved HTML. Returning resolved data renders immediately. Route-data HTTP transport continues to resolve promises before serialization.

Outer Preact Suspense

Await also works inside Preact Suspense during early hydration. Application outlets claim their streamed targets during hydration while retaining the comment structure Preact needs. Component changes remove abandoned markers, including nested markers arriving after initial hydration, so delayed islands cannot restore old content. Await keeps the same inner component when a pending value is replaced by resolved data, including root revalidation. Persistent shell controls are retained. For directly owned pending UI, the fallback prop remains the simpler choice. These stream-ownership guarantees use Smolter’s renderApplication/mountApplication protocol. Manually hydrating arbitrary server fallback markup with a different rejected client promise remains subject to Preact’s hydration behavior; Smolter cannot infer which server boundary produced that markup.

<Suspense fallback={<p>Loading optional data…</p>}>
  <Await resolve={data.optional} errorElement={<p>Optional data unavailable</p>}>
    {(value) => <p>{value}</p>}
  </Await>
</Suspense>

Client-loader promises

From 0.4.6, nested clientLoader promises reach the page without blocking navigation. Use fallback for pending content and errorElement for local rejection. This uses ordinary client rendering, not an SSR document stream, and does not use deferredTimeout. Keep the promise from loader data stable rather than starting a request during render.

const data = useLoaderData<typeof clientLoader>();
return <Await resolve={data!.stats}
  fallback={<p>Loading statistics…</p>}
  errorElement={<p>Statistics unavailable.</p>}>
  {(stats) => <p>{stats.rooms} rooms</p>}
</Await>;
View implementation ↗