How to Use Await Component in Remix for Async UI
In Remix, use the
Await component inside a Suspense boundary to handle promises and show fallback UI while waiting. Wrap your async data with defer() in loaders, then use Await to render the resolved data or handle errors gracefully.Syntax
The Await component is used inside a Suspense component to handle asynchronous data in Remix. It takes a resolve prop which is a promise, and optionally errorElement to show if the promise rejects.
Basic structure:
<Suspense fallback="Loading...">: Shows fallback UI while waiting.<Await resolve={promise} errorElement="<ErrorComponent />">{data => ...}</Await>: Renders children when promise resolves or error UI if it rejects.
tsx
import { Suspense } from 'react'; import { Await } from '@remix-run/react'; function Component({ promise }) { return ( <Suspense fallback={<div>Loading...</div>}> <Await resolve={promise} errorElement={<div>Error loading data</div>}> {(data) => <div>{data.someValue}</div>} </Await> </Suspense> ); }
Example
This example shows how to use defer() in the loader to return a promise, then use Await in the component to render the data once it resolves. It also shows a loading fallback and error handling.
tsx
import { defer } from '@remix-run/node'; import { useLoaderData, Await } from '@remix-run/react'; import { Suspense } from 'react'; // Simulate async data fetch function fetchUser() { return new Promise((resolve) => { setTimeout(() => resolve({ name: 'Alice', age: 30 }), 2000); }); } export async function loader() { return defer({ user: fetchUser() }); } export default function UserProfile() { const data = useLoaderData(); return ( <div> <h1>User Profile</h1> <Suspense fallback={<p>Loading user info...</p>}> <Await resolve={data.user} errorElement={<p>Failed to load user data.</p>} > {(user) => ( <div> <p>Name: {user.name}</p> <p>Age: {user.age}</p> </div> )} </Await> </Suspense> </div> ); }
Output
User Profile
Loading user info... (for 2 seconds)
Name: Alice
Age: 30
Common Pitfalls
- Not wrapping
Awaitinside aSuspensecomponent causes React errors becauseAwaitrelies on suspense fallback. - Passing non-promise values to
resolvewill render immediately but defeats the purpose of suspense. - Forgetting to handle errors with
errorElementleads to unhandled promise rejections and broken UI.
tsx
/* Wrong: Await used without Suspense */ import { Await } from '@remix-run/react'; export default function Wrong() { const promise = fetch('/api/data').then(res => res.json()); return ( <Await resolve={promise}> {(data) => <div>{data.value}</div>} </Await> ); } /* Right: Await inside Suspense with fallback and errorElement */ import { Suspense } from 'react'; import { Await } from '@remix-run/react'; export default function Right() { const promise = fetch('/api/data').then(res => res.json()); return ( <Suspense fallback={<div>Loading...</div>}> <Await resolve={promise} errorElement={<div>Error loading data</div>}> {(data) => <div>{data.value}</div>} </Await> </Suspense> ); }
Quick Reference
| Prop | Description | Required | Type |
|---|---|---|---|
| resolve | The promise to wait for and render | Yes | Promise |
| children | Function that receives resolved data and returns JSX | Yes | (data) => ReactNode |
| errorElement | JSX to show if promise rejects | No | ReactNode |
| fallback | Passed to Suspense to show while waiting | No | ReactNode (on Suspense) |
Key Takeaways
Always wrap
Await inside a Suspense component with a fallback UI.Use
defer() in loaders to return promises that Await can handle.Provide an
errorElement to gracefully handle promise rejections.Pass a promise to
resolve prop; non-promises render immediately without suspense.The children of
Await is a function that receives resolved data to render.