Promises

Streaming response data using a Promise

This example was originally created as one of several examples I wrote for a post about streaming data with promises. See that post for a more detailed explanation of the concepts here.

One cool thing we can do with React Server Components is stream data from server to client using a Promise. From our code's perspective, we can initiate a request on the server, which returns a Promise (e.g. has not actually given us the data yet), and then send that Promise to the client.

The client can then wait for the Promise to resolve, and then render the data. All without holding up the rest of the UI from being rendered. Amazing. [Read more about how this works]

The page is simple - a server component itself, which renders mostly static content except for a data table, which relies on fetching data from somewhere. Our <Table> component accepts dataPromise, which is any Promise that will resolve to an array of objects, in this case after 1000ms delay:

page.tsx
import { Suspense } from 'react'
import Table from './table'
import { getData } from './data'

export default function SuspensePage() {
return (
<div className="mx-4 my-4 flex flex-col gap-4">
<p>This line rendered on the server.</p>
<p>
The table that appears below was rendered on the client, passed a
dataPromise prop that contains its data.
</p>

<Suspense fallback={<div>Loading...</div>}>
<Table dataPromise={getData(1000)} />
</Suspense>
</div>
)
}

The fake database is contemptibly simple:

data.ts
const fakeData = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
]

export async function getData(delay: number): Promise<any> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(fakeData)
}, delay)
})
}

And the <Table> component is a client component, which uses use to wait for the dataPromise to resolve before the component can be fully rendered and passed in to replace the <Suspense> fallback:

table.tsx
'use client'
import { use } from 'react'

export default function Table({ dataPromise }: { dataPromise: Promise<any> }) {
const data = use(dataPromise)

return (
<table className="max-w-5xl table-auto text-left">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{data.map((row: any) => (
<tr key={row.id}>
<td>{row.id}</td>
<td>{row.name}</td>
</tr>
))}
</tbody>
</table>
)
}

Live Demo

This example can't be easily inlined as it demonstrates how a full-page feels to the end user. Here it is inside an iframe, and there's a looping video below too.

This is an iframe pointing to /examples/promises/resolved

Video Preview

In case the iframe doesn't work for some reason, this is a looping video of what you would see. Click the video to open the full page example in a new tab.

Similar Examples

Previous
async, no suspense