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-reactPreview
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
Last updated on