Last active
October 12, 2024 04:41
-
-
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 } 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