DEV Community

Cover image for React.js ~use() hook for loading/error guards~
Ogasawara Kakeru
Ogasawara Kakeru

Posted on

React.js ~use() hook for loading/error guards~

The Pattern It replaces

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    /* ... fetch, cancelled flag, setState ... */
  }, [userId]);

  if (isLoading) return <Skeleton />;
  if (error) return <ErrorMessage error={error} />;
  if (!user) return null;

  return <ProfileCard user={user} />;
}

Enter fullscreen mode Exit fullscreen mode

Three state declareations, one effect, three conditional returns: all before you reach the actual UI.Every component that fetches data repeats this structure.

Here is the same component with use():

// Client Component - only the happy path
"use client";

import { use } from "react";

function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise);
  return <ProfileCard user={user} />;
}
Enter fullscreen mode Exit fullscreen mode
// Server Component - creates the promise and defines the boundaries
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";

export default function UserPage({ params }: { params: { id: string } }) {
  const userPromise = fetchUser(params.id);

  return (
    <ErrorBoundary fallback={<ErrorMessage />}>
      <Suspense fallback={<Skeleton />}>
        <UserProfile userPromise={userPromise} />
      </Suspense>
    </ErrorBoundary>
  );
}
Enter fullscreen mode Exit fullscreen mode

The loading state is handled by <Suspense>.The error state is handled by <ErrorBoundary>(from the react-error-boundary package). The component itself only contains the happy path - the code that runs when data is available. The state machine has been moved from your code into React's runtime.

Because UserPage is a Server Component, it does not re-render. The promise reference is created once and passed down as a stable prop, no caching gymnastics needed.

Separation of Concerns

Notice how the component that uses the data (UserProfile) is separated from the component that initiates the fetch and defines the loading/error UI (UserPage). This is intentional. The consumer doesn't know where the promise came from or what to show while waiting.

Top comments (0)