Last active
April 23, 2023 00:39
-
-
Save markmals/709f3bd3ab2e698710bb82a42f00ccaf to your computer and use it in GitHub Desktop.
A sketch idea of Preact Server Components with coarse-grained progressive hydration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useSignal } from "@preact/signals" | |
import { action$, island$, json, loader$ } from "preact-server-components" | |
import { useEffect } from "preact/hooks" | |
import { db } from "./db.server" | |
const Counter = island$(() => { | |
let count = useSignal(0) | |
// TODO: Resumable hydration | |
// We don't want to do this work on the server on initial load and then again on | |
// the client during hydration (replayable hydration). Instead, we should serialize | |
// the state after initial run on the server and enable the client to deserialize | |
// that state and resume where the server left off without doing the work again | |
// (resumable hydration). | |
useEffect(() => { | |
count.value++ | |
}, []) | |
return <button onClick={() => count.value++}>Increment count: {count}</button> | |
}) | |
export const useMessage = loader$(async ({ params: { id } }) => { | |
return json(await db.message.findFirst({ where: { id } })) | |
}) | |
export default function Message() { | |
const message = useMessage() | |
return ( | |
<> | |
{/* This component will be hydrated when the user clicks on it: */} | |
<Counter client:click /> | |
{/* This DOM node will just be statically rendered in the HTML output using */} | |
{/* the initial values from the initial server load. */} | |
{/* It will never be hydrated because it's not an island: */} | |
<span>{message.localizedDescription}</span> | |
</> | |
) | |
} | |
export const useEcho = action$(async ({ request }) => { | |
let data = await request.formData() | |
let message = data.get("message") as string | |
// Imagine this is a call to fetch | |
await new Promise(resolve => setTimeout(resolve, 1000)) | |
return json({ message }) | |
}) | |
export const Echo = island$(() => { | |
const [echoing, echo] = useEcho() | |
return ( | |
<form> | |
<input onChange={event => echo(event.currentTarget.form)} /> | |
{!(echoing.state === "loading") && echoing.result.message} | |
</form> | |
) | |
}) | |
export const useLogin = action$(async ({ request }) => { | |
await new Promise(resolve => setTimeout(resolve, 1000)) | |
let formData = await request.formData() | |
let username = formData.get("username") | |
if (username === "admin") { | |
return redirect("/admin") | |
} else { | |
throw redirect("/home", { statusText: "Invalid username" }) | |
} | |
}) | |
export function Login() { | |
const [_, { Form }] = useLogin() | |
return ( | |
<Form> | |
<label for="username">Username:</label> | |
<input type="text" name="username" /> | |
<input type="submit" value="submit" /> | |
</Form> | |
) | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment