So we've got our stocks app set up with Suspense, but let's make it more granular. This code can be found in the stocks-with-suspense-granular
directory.
First, we'll remove the fallback from the page.tsx
suspense block since we're going to make it more granular:
// inside of the Home component return:
<Suspense>
<Dashboard />
</Suspense>
Over in dashboard.tsx
we'll get rid of the Promise.all
and instead fetch each stock individually. To do this, we'll create a StockDisplay
component that takes a stockPromise
as a prop and uses the use
hook to wait for the promise to resolve:
// inside of app/dashboard.tsx
import { Suspense, use } from "react";
import { getStocks } from "@/lib/getStocks";
import { StockInfo } from "@/types";
function StockDisplay({ stockPromise }: { stockPromise: Promise<StockInfo> }) {
const { name, ui } = use(stockPromise);
return (
<div>
<div className="font-bold text-3xl">{name}</div>
<div>{ui}</div>
</div>
);
}
Next, we'll use the StockDisplay
component in the Dashboard
component. Surrounding it with a Suspense
block will make it more granular:
// inside of app/dashboard.tsx
export default async function Dashboard() {
const stocks = await getStocks();
return (
<div className="grid grid-cols-3 gap-4 mt-5">
{stocks.map((stockPromise, index) => (
<Suspense key={index} fallback={<div>Loading...</div>}>
<StockDisplay stockPromise={stockPromise} />
</Suspense>
))}
</div>
);
}
Now, each stock will load independently, and we'll see a loading indicator for each stock that hasn't loaded yet. That's much better! We've achieved more granular suspense in our React app!
In the next lesson, we'll look at how to do this with server actions. That's where it gets really interesting as we start creating our own DIY streaming system from the server. Server actions can return UI, so we can return interactive elements right from the server action.