MENU
useAsyncValue
A loader normally has to await everything before the route can render, but that means the whole page waits on the slowest piece of data. To stream a slow piece in separately, return it from the loader as a plain, un-awaited promise alongside whatever you do await – there's no defer() wrapper needed anymore, a plain object with a promise-valued property is enough.
Render that promise with <Await resolve={promise}>, wrapped in a React <Suspense> boundary for its fallback. Whatever's inside <Await> can read the resolved value with useAsyncValue(). If the promise rejects instead, <Await>'s own errorElement renders and can read the rejection reason with useAsyncError() – or, if <Await> has no errorElement of its own, the rejection bubbles up to the route's own error boundary instead, readable there via useRouteError(). Like the rest of the hooks in this section, all of this only applies underneath a data router (see the Routers section).
import React, { Suspense } from "react";
import {
createBrowserRouter, useLoaderData, Await, useAsyncValue, useAsyncError,
} from "react-router";
import { RouterProvider } from "react-router/dom";
function getPost() {
return Promise.resolve({ title: "Streaming Data With Await" });
}
function getComments() {
// Simulates a slower request that resolves after the page's
// shell has already rendered.
return new Promise((resolve) => {
setTimeout(() => resolve(["Nice write-up!", "Very clear, thanks."]), 1500);
});
}
const router = createBrowserRouter([
{
path: "/",
element: <Post />,
loader: async () => {
let post = await getPost();
let comments = getComments(); // intentionally not awaited
return { post, comments };
},
},
]);
export default function UseAsyncValueExample() {
return <RouterProvider router={router} />;
}
function Post() {
let { post, comments } = useLoaderData();
return (
<div>
<h3>{post.title}</h3>
<Suspense fallback={<p>Loading comments...</p>}>
<Await resolve={comments} errorElement={<CommentsError />}>
<Comments />
</Await>
</Suspense>
</div>
);
}
function Comments() {
let comments = useAsyncValue();
return (
<ul>
{comments.map((comment, i) => <li key={i}>{comment}</li>)}
</ul>
);
}
function CommentsError() {
let error = useAsyncError();
return <p>Comments failed to load: {error.message}</p>;
}post.title is awaited in the loader, so it's already there on the first render. comments is not awaited, so Post renders immediately with the Suspense fallback in its place, then swaps in Comments (which calls useAsyncValue() to get the resolved array) as soon as getComments() resolves 1.5 seconds later. Had that promise rejected instead, CommentsError would render in its place and read the rejection reason with useAsyncError().