Using AsyncResult.builder

Avatar of Hemanta SundarayHemanta Sundaray

Effect Atom provides another API for handling async states: AsyncResult.builder.

Replace the existing code inside components/user-grid.tsx with the following:

components/user-grid.tsx
"use client";
import { useAtomValue } from "@effect/atom-react";
import { AsyncResult, Atom } from "effect/unstable/reactivity";
import { FailureCard } from "@/components/failure-card";
import { UserEmptyCard } from "@/components/user-empty-card";
import { UserGridSpinner } from "@/components/user-grid-spinner";
import { UserSuccessCard } from "@/components/user-success-card";
import { usersAtom } from "@/atoms/user";
const selectUsers = (result: Atom.Type<typeof usersAtom>) =>
AsyncResult.map(result, (data) => data.users);
export function UserGrid() {
const usersResult = useAtomValue(usersAtom, selectUsers);
return AsyncResult.builder(usersResult)
.onInitial(() => <UserGridSpinner />)
.onErrorTag("ClientError", (error) => (
<FailureCard title="Client Error" message={error.message} />
))
.onErrorTag("ServerError", (error) => (
<FailureCard title="Server Error" message={error.message} />
))
.onErrorTag("ParseError", (error) => (
<FailureCard title="Parse Error" message={error.message} />
))
.onDefect(() => (
<FailureCard
title="Unexpected Error"
message="Something went wrong. Please try refreshing the page."
/>
))
.onSuccess((users) =>
users.length === 0 ? (
<UserEmptyCard reason="empty-users-list" />
) : (
<div className="grid grid-cols-1 gap-10 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{users.map((user) => (
<UserSuccessCard key={user.id} user={user} />
))}
</div>
),
)
.render();
}

AsyncResult.builder provides two advantages over AsyncResult.match:

Fine-grained error handling

Instead of handling all errors in a single onFailure callback, you can use onErrorTag to handle specific error types individually. This lets you show different UI for different errors.

Access to the waiting state.

Each handler receives metadata as its second parameter, including a waiting boolean, which indicates whether data is being refreshed in the background. We’ll explore the waiting boolean in detail in the Showing Stale Data During Refresh chapter.

Note

Unlike AsyncResult.match, AsyncResult.builder doesn’t guarantee exhaustiveness. TypeScript won’t error if you forget to handle an error type.

Last updated on March 19, 2026

Sign in to save progress

Stay in the loop

Get notified when new Effect Atom related content is published.