Using Result.match

Avatar of Hemanta SundarayHemanta Sundaray

Currently, we’re rendering the raw structure of the Result object to visualize its internal state. Now that we understand the three states a Result can be in, let’s replace the JSON dump with a proper UI.

What does a proper UI for async state look like?

We need to account for three states:

  • Initial: Data fetching is in progress. We show a spinner.
  • Failure: Data fetching failed. We show the error name and message.
  • Success: Data fetching succeeded. We render the users.

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

components/user-grid.tsx
"use client";
import { Result, useAtomValue } from "@effect-atom/atom-react";
import { Cause } from "effect";
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";
import { ConfigError, GetUsersError } from "@/errors";
import { UsersResponse } from "@/schema/user-schema";
import { getErrorInfo } from "@/lib/utils";
const selectUsers = (
result: Result.Result<UsersResponse, ConfigError | GetUsersError>,
) => Result.map(result, (data) => data.users);
export function UserGrid() {
const usersResult = useAtomValue(usersAtom, selectUsers);
return Result.match(usersResult, {
onInitial: () => <UserGridSpinner />,
onFailure: (failure) => {
const error = Cause.squash(failure.cause);
const { title, message } = getErrorInfo(error);
return <FailureCard title={title} message={message} />;
},
onSuccess: (success) => {
const users = success.value;
if (users.length === 0) {
return <UserEmptyCard reason="empty-users-list" />;
}
return (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-10">
{users.map((user) => (
<UserSuccessCard key={user.id} user={user} />
))}
</div>
);
},
});
}
Note

If you see a React hooks order error after saving, do a full page refresh. This happens because the previous version of UserGrid used useAtomValue without a transform function, and the new version uses one. The different code paths call different hooks, which triggers a React warning during hot reload.

Let’s understand what’s going on here.

The UserGrid component needs the users array. But usersAtom contains both the users array and the users count. So, we pass a transform function named selectUsers as the second argument to useAtomValue. selectUsers takes in the full Result and uses Result.map to extract just the users array. This way, usersResult contains only the users.

Result.match takes a Result and an object with three handlers, one for each state:

  • onInitial renders a spinner while data is being fetched.

  • onSuccess receives the success value (the users array) and renders the user cards, or an empty state if the array is empty.

  • onFailure deserves a closer look. The failure result contains a cause property, but this isn’t the error directly. It’s an Effect Cause, which is a wrapper that can represent complex error scenarios like multiple failures or interruptions. Cause.squash extracts the underlying error from this wrapper. Once we have the error, we pass it to getErrorInfo, which reads the error’s name and message to produce a user-friendly title and description for the FailureCard.

Note

Result.match guarantees exhaustiveness. TypeScript will error if you forget to handle any of the three states. Try commenting out one of the handlers to see this in action.

Now, visit the home page. You should see 12 user cards rendered. To see the FailureCard in action, go to user-service.ts and change the endpoint from /users to /usersx (which doesn’t exist). Visit the home page again. You should see the failure card rendered with the error title and message. Remember to revert the endpoint back to /users before continuing.

Sign in to save progress

Stay in the loop

Get notified when new chapters are added and when this course is complete.