Skip to content

Instantly share code, notes, and snippets.

@AndrewIngram
Last active March 6, 2023 02:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AndrewIngram/09cac18028d260582ebb77fd3e787b37 to your computer and use it in GitHub Desktop.
Save AndrewIngram/09cac18028d260582ebb77fd3e787b37 to your computer and use it in GitHub Desktop.
Sketch of migrating eager to lazy popovers with RSC + Server Actions

A simple sketch of how we migrate a "card" component to lazy-load its popover content by server actions. The after (lazy) version is visibly more complex, but the steps involved are fairly straightforward -- and if it were a common pattern, there are steps that could be taken to abstract it into something simpler.

Important to note that if it's determined that server actions can only be defined in server components, the solution would be a little different.

import { Course } from "~/types";
import {
Popover,
PopoverArrow,
PopoverContent,
PopoverTrigger,
} from "~/components/Popover";
import { CourseRepo } from "./repo";
export default async function CourseCard({ course }: { course: Course }) {
// This will load the uploader data on render, this is wasteful if we
// never actual open the popover
const uploader = await CourseRepo.getPlayerInfo(course.uploader.code);
return (
<Popover>
<div>
<PopoverContent>
<p>{uploader.name}</p>
<PopoverArrow />
</PopoverContent>
<h3>
{course.name} <PopoverTrigger>Uploader</PopoverTrigger>
</h3>
<p>{course.description}</p>
</div>
</Popover>
);
}
"use client";
import { Course, Player } from "~/types";
import {
Popover,
PopoverArrow,
PopoverContent,
PopoverTrigger,
} from "~/components/Popover";
import { CourseRepo } from "./repo";
import { Suspense, useState } from "react";
import Await from "~/components/Await";
export default function CourseCard({ course }: { course: Course }) {
// We've switched to a client component with state for the uploader data instead.
const [pendingUploader, setPendingUploader] = useState<
Promise<Player> | Promise<void>
>(new Promise());
// We define an inline server action to load the data. Without server
// actions, we'd need to manually expose this capabality via an HTTP API.
// Instead, server actions take care of all that behind the scenes.
//
// This doesn't *need* to be inline, we could parameterise rather than
// closing over local variables. It's inline here to illustrate how local
// the code can feel.
function loadUploader() {
"use server";
return CourseRepo.getPlayerInfo(course.uploader.code);
}
// When the popover first opens, we kick off the load, and assign the promise to state
// The "Await" component suspends until the promise is resolved, then renders the loaded
// data as the popover content, just like the original
return (
<Popover onOpenChange={() => setPendingUploader(loadUploader())}>
<div>
<PopoverContent>
<Suspense fallback="Loading...">
<Await resolve={pendingUploader}>
{(uploader) => <p>{uploader.name}</p>}
</Await>
</Suspense>
<PopoverArrow />
</PopoverContent>
<h3>
{course.name} <PopoverTrigger>Uploader</PopoverTrigger>
</h3>
<p>{course.description}</p>
</div>
</Popover>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment