Skip to content

Instantly share code, notes, and snippets.

@markmals
Last active October 12, 2024 04:41
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 } from 'react';
import { db, Messages } from './db.server';
import { defineRoute, withState } from 'reverb';
import { eq } from 'drizzle-orm';
function Counter() {
'use client';
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>Increment count: {count}</button>;
}
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({ clientId }: { clientId: number }) {
'use client';
const resolvedMessage = use(cachedMessage(clientId));
return (
<>
<Counter />
<span>{resolvedMessage.localizedDescription}</span>
</>
);
}
export async function MessageServer({ clientId }: { clientId: number }) {
const resolvedMessage = await cachedMessage(clientId);
return (
<>
{/* This component will be hydrated */}
<Counter />
{/* 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 a client component. */}
<span>{resolvedMessage.localizedDescription}</span>
</>
);
}
async function echo(message: string) {
'use server';
// Imagine this is a call to fetch
await new Promise(resolve => setTimeout(resolve, 1000));
return { message };
}
function Echo() {
'use client';
const [state, dispatch, isPending] = useActionState(withState(echo), {
message: 'Awaiting Message',
});
return (
<>
<input type="text" onChange={event => dispatch(event.currentTarget.value)} />
{!isPending && state.message}
</>
);
}
async function login(data: FormData) {
'use server';
await new Promise(resolve => setTimeout(resolve, 1000));
const username = data.get('username');
if (username === 'admin') {
return Response.redirect('/admin');
} else {
// Return components here instead of redirect with a message?
// throw Response.redirect('/home', { statusText: 'Invalid username' });
}
}
export function Login({ id }: { id: number }) {
return (
<form action={login}>
<MessageServer clientId={id} />
<Suspense>
<MessageClient clientId={id} />
</Suspense>
<label htmlFor="username">Username:</label>
<input type="text" name="username" />
<input type="submit" value="submit" />
</form>
);
}
export default defineRoute({
params: ['id', 'brand?'],
head: {
links: () => [{ rel: 'stylesheet', href: 'styles.url' }],
meta: () => [{ title: 'My Route' }],
},
async loader({ params }) {
const id = parseInt(params.id);
await cachedMessage(id);
return <Login id={id} />;
},
async error({ error }) {
return <div>Received error: {(error as any).localizedDescription}</div>;
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment