Skip to content

Instantly share code, notes, and snippets.

@markmals
Last active February 7, 2025 00:55
Show Gist options
  • Save markmals/97014cd5d0ebf36a0f34a1c44514ab53 to your computer and use it in GitHub Desktop.
Save markmals/97014cd5d0ebf36a0f34a1c44514ab53 to your computer and use it in GitHub Desktop.
Playing around with ideas for a 'close to the metal' React framework
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