Await

Handle async operations with declarative loading, error, and success states.

The Await component provides a declarative way to handle asynchronous operations in React, managing loading states, errors, and successful data resolution with a clean, composable API.

Installation

npm install @zayne-labs/ui-react

Preview

Loading...

Usage

For simple use cases, you can pass a fallback directly to the root.

import { Await } from "@zayne-labs/ui-react/common/await";

function UserProfile({ userId }) {
  const userPromise = fetchUser(userId);

  return (
    <Await.Root promise={userPromise} fallback={<Skeleton />}>
      {(user) => (
        <div>
          <h1>{user.name}</h1>
          <p>{user.email}</p>
        </div>
      )}
    </Await.Root>
  );
}

Use sub-components for fine-grained control over each promise state.

import { Await } from "@zayne-labs/ui-react/common/await";

function ProductHero({ productPromise }) {
  return (
    <Await.Root promise={productPromise}>
      <Await.Pending>
        <HeroSkeleton />
      </Await.Pending>

      <Await.Error>
        {({ error }) => <p className="text-red-500">Failed: {error.message}</p>}
      </Await.Error>

      <Await.Success>
        {(product) => <Hero data={product} />}
      </Await.Success>
    </Await.Root>
  );
}

Component Source

"use client";import {	getSlotMap,	withSlotNameAndSymbol,	type GetSlotComponentProps,} from "@zayne-labs/toolkit-react/utils";import { isFunction } from "@zayne-labs/toolkit-type-helpers";import { Fragment as ReactFragment, Suspense, use, useMemo } from "react";import { ErrorBoundary, useErrorBoundaryContext, type ErrorBoundaryProps } from "../error-boundary";import { Slot } from "../slot";import type { SuspenseWithBoundaryProps } from "../suspense-with-boundary";import { AwaitContextProvider, useAwaitContext } from "./await-context";type RenderPropFn<TValue> = (result: TValue) => React.ReactNode;type ChildrenType<TValue> = React.ReactNode | RenderPropFn<TValue>;export type AwaitRootProps<TValue> = Omit<SuspenseWithBoundaryProps, "children"> & {	asChild?: boolean;	children: ChildrenType<TValue>;	promise: Promise<TValue>;	withErrorBoundary?: boolean;	withSuspense?: boolean;};export function AwaitRoot<TValue>(props: AwaitRootProps<TValue>) {	const {		asChild,		children,		errorFallback,		errorResetKeys,		fallback,		name,		onError,		onErrorReset,		promise,		withErrorBoundary = true,		withSuspense = true,	} = props;	const WithErrorBoundary = withErrorBoundary ? ErrorBoundary : ReactFragment;	const WithSuspense = withSuspense ? Suspense : ReactFragment;	const slots =		!isFunction(children) ?			getSlotMap<SlotComponentProps>(children)		:	({ default: children } as unknown as ReturnType<typeof getSlotMap<SlotComponentProps>>);	const resolvedPendingFallback = slots.pending ?? fallback;	const resolvedErrorFallback = slots.error ?? errorFallback;	return (		<WithErrorBoundary			{...(withErrorBoundary && {				errorFallback: resolvedErrorFallback,				errorResetKeys,				onError,				onErrorReset,			})}		>			<WithSuspense {...(withSuspense && { fallback: resolvedPendingFallback, name })}>				<AwaitRootImpl promise={promise} asChild={asChild}>					{slots.default}				</AwaitRootImpl>			</WithSuspense>		</WithErrorBoundary>	);}type AwaitRootImplProps<TValue> = Pick<AwaitRootProps<TValue>, "asChild" | "children" | "promise">;function AwaitRootImpl<TValue>(props: AwaitRootImplProps<TValue>) {	const { asChild, children, promise } = props;	const result = use(promise);	const resolvedChildren = isFunction(children) ? children(result) : children;	const Component = asChild ? Slot.Root : ReactFragment;	const contextValue = useMemo(() => ({ promise, result }), [promise, result]);	return (		<AwaitContextProvider value={contextValue}>			<Component {...(asChild && contextValue)}>{resolvedChildren}</Component>		</AwaitContextProvider>	);}type SlotComponentProps = AwaitErrorProps | AwaitPendingProps | AwaitSuccessProps;type AwaitSuccessProps<TValue = unknown> = GetSlotComponentProps<"default", ChildrenType<TValue>>;export function AwaitSuccess<TPromiseOrValue, TValue = Awaited<TPromiseOrValue>>(	props: Pick<AwaitSuccessProps<TValue>, "children">) {	const { children } = props;	if (isFunction(children)) {		// eslint-disable-next-line react/rules-of-hooks, react-hooks/hooks -- This hook only uses `use` under the hood so this is safe		const { result } = useAwaitContext<TValue>();		return children(result);	}	return children;}Object.assign(AwaitSuccess, withSlotNameAndSymbol<AwaitSuccessProps>("default"));type AwaitErrorProps = GetSlotComponentProps<"error", ErrorBoundaryProps["errorFallback"]>;export const AwaitError = withSlotNameAndSymbol<AwaitErrorProps, { asChild?: boolean }>(	"error",	(props) => {		const { asChild, children } = props;		const errorBoundaryContext = useErrorBoundaryContext();		const Component = asChild ? Slot.Root : ReactFragment;		const resolvedChildren = isFunction(children) ? children(errorBoundaryContext) : children;		return <Component {...(asChild && errorBoundaryContext)}>{resolvedChildren}</Component>;	});type AwaitPendingProps = GetSlotComponentProps<"pending", React.SuspenseProps["fallback"]>;export const AwaitPending = withSlotNameAndSymbol<AwaitPendingProps>("pending");

API Reference

Edit on GitHub

Last updated on

On this page