Skip to content

Instantly share code, notes, and snippets.

@markmals
Last active April 23, 2023 00:39
Show Gist options
  • Save markmals/709f3bd3ab2e698710bb82a42f00ccaf to your computer and use it in GitHub Desktop.
Save markmals/709f3bd3ab2e698710bb82a42f00ccaf to your computer and use it in GitHub Desktop.
A sketch idea of Preact Server Components with coarse-grained progressive hydration
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