Reputation: 38652
I'm playing around with Next.js 13's app
dir, and I want to replicate some functionality I have with the "classic" pages
dir. I want to display a grid of cards, one each for a set of hosts. I want to ping the hosts in the background, and each card should show whether the host is reachable or not.
So a card looks like this:
app/clients/client-card.tsx
:
"use client";
import { Card, CardBody } from "../components";
import { Chip } from "@material-tailwind/react";
export type ClientProps = {
label: string;
available?: boolean | undefined;
};
export default function ClientCard({ label, available }: ClientProps) {
return (
<Card key={label} title={label}>
<CardBody>
<h4>{label}</h4>
<Chip
color={available === undefined ? "gray" : available ? "green" : "red"}
className="mt-2"
value={available === undefined ? "Checking" : available ? "Online" : "Offline"}
/>
</CardBody>
</Card>
);
}
And the server code in app/clients/page.tsx
:
import fs from "fs";
import path from "path";
import { load } from "js-yaml";
import ClientCard from "./client-card";
type AnsibleInventory = {
probes?: {
hosts: Record<string, unknown>;
};
};
async function getInventory() {
const inventoryFile = path.join(
__dirname,
"..",
"..",
"..",
"..",
"..",
"example",
"inventory",
"hosts.yml"
);
const inventory = load(
fs.readFileSync(inventoryFile, "utf8")
) as AnsibleInventory;
return inventory;
}
export default async function Clients() {
const inventory = await getInventory();
const isAvailable = async (client: string): Promise<boolean> => {
const sleep = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
await sleep(1000 + Math.random() * 1000);
return Math.random() > 0.5;
};
return (
<div>
<h1>Clients</h1>
{inventory.probes?.hosts === undefined ? (
<p>No clients found</p>
) : (
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{Object.keys(inventory.probes?.hosts).map((client) => (
<div key={client} className="col-span-1">
<ClientCard
key={client}
label={client}
available={isAvailable(client)}
/>
</div>
))}
</div>
)}
</div>
);
}
The problem is, I want the page to load initially first, with each card showing a "Checking" state and gray chip, and propagate the results of the check. to each of the cards later on. But I can't asynchronously call the isAvailable()
function—it needs a synchronous value. And I can't use useState
and effects on the server side.
Previously I would have probably created a dedicated api
route and made a request from each client-side card to the server to fetch
the availability status from the API.
Is there any simple way to do this? In particular, this is a potentially long list of cards for individual hosts …
I see that in one of the streaming examples, a fetch
is passed to the individual components, but those appear to be server-side, too. In my case, I apparently must use "use client";
due to some client-side effects being used by the @material-tailwind/react
library, so I can't make it a server component.
Upvotes: 1
Views: 389
Reputation: 38652
There seems to be no obvious solution for now, other than to separate the libraries such that I don't import a client side library in the RSC.
Upvotes: 0