Last active
February 7, 2025 00:55
-
-
Save markmals/97014cd5d0ebf36a0f34a1c44514ab53 to your computer and use it in GitHub Desktop.
Playing around with ideas for a 'close to the metal' React framework
This file contains 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 { Suspense, useState, use, useActionState, cache, JSX, Fragment, ReactNode } from "react"; | |
import { defineRoute, withState } from "reverb"; | |
import { db, Messages, Users } from "./db.server"; | |
import { eq } from "drizzle-orm"; | |
function Counter() { | |
"use client"; | |
const [count, setCount] = useState(0); | |
return ( | |
<Fragment> | |
<div>{count}</div> | |
<button onClick={() => setCount(c => c + 1)}>Increment</button> | |
</Fragment> | |
); | |
} | |
async function message(id: number) { | |
"use server"; | |
const message = await db.select().from(Messages).where(eq(Messages.id, id)).get(); | |
return message ?? { localizedDescription: "Message not found" }; | |
} | |
const cachedMessage = cache(message); | |
function MessageClient({ id }: { id: number }) { | |
"use client"; | |
const resolvedMessage = use(cachedMessage(id)); | |
return ( | |
<Fragment> | |
<Counter /> | |
<span>{resolvedMessage.localizedDescription}</span> | |
</Fragment> | |
); | |
} | |
export async function MessageServer({ id }: { id: number }) { | |
const resolvedMessage = await cachedMessage(id); | |
return ( | |
<Fragment> | |
{/* This component will be hydrated */} | |
<Counter /> | |
{/* This vDOM node will just be statically rendered in the JSON payload */} | |
{/* with the initial values from the initial server load. */} | |
{/* It will never be hydrated because it's not a client component. */} | |
<span>{resolvedMessage.localizedDescription}</span> | |
</Fragment> | |
); | |
} | |
async function echo(message: string) { | |
"use server"; | |
await new Promise(resolve => setTimeout(resolve, 1000)); | |
return { message }; | |
} | |
function Echo() { | |
"use client"; | |
const [state, dispatch, isPending] = useActionState(withState(echo), { | |
message: "Awaiting Message", | |
}); | |
return ( | |
<Fragment> | |
<input type="text" onChange={event => dispatch(event.currentTarget.value)} /> | |
{!isPending && state.message} | |
</Fragment> | |
); | |
} | |
function ErrorBanner({ text }: { text: string }) { | |
return <div data-banner="error">Error: {text}</div>; | |
} | |
async function login(data: FormData): Promise<JSX.Element | null> { | |
"use server"; | |
const username = data.get("username") as string | null; | |
const users = await db | |
.select() | |
.from(Users) | |
.where(eq(Users.username, username ?? "")); | |
if (username === "admin") { | |
throw Response.redirect("/admin"); | |
} else if (users?.length > 0) { | |
throw Response.redirect(`/account/${username}`); | |
} else { | |
return <ErrorBanner text="Invalid Username" />; | |
} | |
} | |
function LoginForm({ children }: { children: ReactNode }) { | |
"use client"; | |
const [errorBanner, loginAction, isPending] = useActionState(withState(login), null); | |
return ( | |
<form action={loginAction}> | |
{children} | |
<button type="submit" disabled={isPending}> | |
Log in | |
</button> | |
{isPending ? null : errorBanner} | |
</form> | |
); | |
} | |
export function Login() { | |
return ( | |
<Fragment> | |
<h2>Log In</h2> | |
<LoginForm> | |
<label htmlFor="username">Username:</label> | |
<input type="text" name="username" /> | |
</LoginForm> | |
</Fragment> | |
); | |
} | |
import styles from "./index.css?url"; | |
export default defineRoute({ | |
params: ["id", "brand?"], | |
head: { | |
links: () => [{ rel: "stylesheet", href: styles }], | |
meta: () => [{ title: "My Route" }], | |
}, | |
loader: async ({ params, outlet }) => { | |
const id = parseInt(params.id); | |
// Load cached data at the route level | |
// before fetching within components | |
await cachedMessage(id); | |
return ( | |
<Fragment> | |
<h1> | |
Welcome {params.brand ? `to ${params.brand}` : "Home"}! | |
<MessageServer id={id} /> | |
<Suspense fallback={<div data-progress="indeterminate">Loading...</div>}> | |
<MessageClient id={id} /> | |
</Suspense> | |
</h1> | |
<Login /> | |
{outlet} | |
</Fragment> | |
); | |
}, | |
error: async ({ error }) => <div>Received error: {error.message}</div>, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment