Search

1,917 code results for react client side

Code
1,917

vals into collections. Built as a Val Town HTTP val with a React frontend.
- Add / remove vals from collections
- Click a collection in the sidebar to filter the main list to only the vals in that collection
- "Add more vals" section appears when viewing a specific collection (lists vals not yet in it)
- Optimistic UI updates for instant feedback (no full page reloads on add/remove)
- Fully server-rendered initial HTML shell + client-side React for interactivity
- Works with your real private data (no demo mode)
### Creating a Collection
- Use the form in the sidebar (under "COLLECTIONS").
- Type a name and submit. The new collection appears immediately (optimistic) and is auto-select
### Adding / Removing Vals
- Click a collection in the sidebar → the grid filters to only its current members (with "Remove
- Below the filtered list you'll see an "Add more vals" section with the rest of your vals (each
### Tips
- The sidebar always shows live counts.
- Cards link to the actual val on Val Town.
`index.ts` – Hono HTTP handler. Serves the HTML shell, the React client at `/client.js`, and all
- `Dashboard.tsx` – Tiny HTML shell (CDNs + root div + script tag).
- `client.ts` – Exports the full client React app as a string (served as `/client.js`). This is
- Collection rows are filtered by OAuth user ID, so stale or copied rows from another user are n
- Feel free to customize the React UI in `client.ts`, add more API routes, improve the server re
- **Backend**: Val Town + Hono + `std/oauth`. Vals loaded via the official SDK (`me.vals.list`)
- **Frontend**: React 18 (loaded via UMD CDN for zero-build). Talks to the same val's `/api/*` r
- **Why this works per-user**: OAuth provides the current user's access token and user ID. The S
- **Sections** (presentational grouping)
- **Hybrid** (sidebar collections + quick filters with filtering)
These were the original static HTML mocks before the live React version.
## Status
create collections and move vals around with a reasonable React UX) but "not perfect yet" – som
A simple community message board application built with React Router 7, demonstrating a full-sta
- **Server-Side Rendering**: All pages are server-rendered when accessed directly
- **Client-Side Navigation**: After initial load, navigation happens client-side
- **Topic Management**: Create and view discussion topics
│ ├── index.html # HTML template
│ └── index.tsx # Client entry point
├── routes/
### Server-Side Rendering
The application uses React Router 7's `createStaticHandler`, `createStaticRouter`, and `StaticRo
2. It runs the appropriate loaders/actions for the matched route
3. The page is rendered with the data and sent to the client
4. The client hydrates the page with `createBrowserRouter` and `RouterProvider`
### Client-Side Navigation
After the initial page load, navigation happens entirely on the client:
1. React Router handles link clicks and form submissions
2. Data is fetched asynchronously using loaders and actions
- **Hono**: For server-side routing and handling
- **React Router 7**: For both server and client-side routing
- **SQLite**: For data persistence
g as a reference for building full-stack applications with React Router 7. The project uses a Re
# React Router Starter
Minimal React Router starter example for Val Town
The `http.tsx` file is the main server that responds with an HTML stream.
L includes a `<script type="module">` tag that loads the `client.tsx` file to hydrate the app
with client-side routing provided by React Router's declarative mode.
This val is a client-side-rendered React app that makes requests to @stevekrouse/cors_example_ba
/** @jsxImportSource https://esm.sh/react@19.2.0 */
// kit.tsx — Small shared pieces reused across every info page: the top-level
// page layout (PageShell/Panel), a reference link row, and a "try the live
// API" card (button + output area wired by client.ts via id convention
// `${id}-btn` / `${id}-out`).
* width at 1120px and centers it — AppShell's content area spans the full
* window between the sidebar and the screen edge, which reads as
* un-anchored on wide monitors. Vertical rhythm between a page's top-level
*/
export function PageShell({ children }: { children: React.ReactNode }) {
return (
* un-padded edge rather than lining up with its siblings (confirmed via
* getBoundingClientRect — every page here nests it inside a padded
* container, so every Section-framed block sat ~20-30px left of everything
*/
export function Panel({ children, id }: { children: React.ReactNode; id?: string }) {
return (
/** @jsxImportSource https://esm.sh/react@19.2.0 */
// app.tsx — SSR root: one Astryx AppShell, one page per request, no client
// router. Theme mode is resolved from a cookie server-side — zero-FOUC, no JS.
import { AppShell, neutralTheme, renderToString, Theme } from "./astryx/components.ts";
import { AppSideNav } from "./astryx/shell.tsx";
import { clientJs } from "./client.ts";
import type { AppProps } from "./types.ts";
const PAGES: Record<string, (props: AppProps) => React.ReactNode> = {
home: HomePage,
{
/* Single expression child only — React 19's <title> hoisting silently
drops all content when given multiple children (e.g. an
/* Utility CSS Astryx doesn't ship, or can't reach without a build
step: .hiddenclient.ts toggles it to show/hide demo tables
before their first successful fetch; .field-input — styles the
field radius, border, background, and focus-ring tokens instead
of looking like unstyled browser chrome; .astryx-side-nav /
.astryx-side-nav-item — Astryx's own stable component classes
(see `astryx theme targets side-nav`), given real padding/gap
here because neither is exposed as a SideNav prop and this repo
has no `astryx theme build` step to compile a defineTheme
page's reading width (see pages/kit.tsx's PageShell); .theme-*
— the sidebar's theme-mode <details>/<summary> dropdown (see
shell.tsx's ThemeForm) styled to match Button/Card chrome. */
outline-offset:var(--focus-outline-offset)}
.astryx-side-nav{padding:8px}
.astryx-side-nav-item{margin-block:2px}
.page-shell{max-width:1120px;margin-inline:auto}
<AppShell
sideNav={
<AppSideNav
appName={props.appName}
</Theme>
<script dangerouslySetInnerHTML={{ __html: clientJs }} />
</body>
/** @jsxImportSource https://esm.sh/react@19.2.0 */
// page-reminders.tsx — Reminders: the flagship live demo. A shared list every
// signed-in visitor can add to and check off — the "family app" this whole
// Quickstart is sized for. Rows are rendered client-side (client.ts) from
// /api/reminders, cloned from the hidden <template> below so every row gets
// full Astryx Button/Badge chrome without needing React hydration.
{
/* Hidden flag client.ts reads to decide whether to render per-row
action buttons — SSR knows the signed-in state, the client-side
row renderer doesn't (rows arrive from a JSON fetch). */
{
/* Real Astryx-rendered row, never displayed — client.ts clones its
<tr> for every reminder so dynamically-inserted rows get full
Button/Badge component chrome without hydrating React. */
}
/** @jsxImportSource https://esm.sh/react@19.2.0 */
// shell.tsx — Side navigation, user section, and the theme-mode switcher.
// Real <a href> navigation (server-rendered, per-route) — no client router.
HStack,
SideNav,
SideNavHeading,
SideNavItem,
SideNavSection,
Text,
// Docs — three small titled sections instead of one collapsible tree. This
// page has zero React hydration (see app.tsx), and Astryx's SideNavItem
// collapse toggle is driven by internal onClick state — a collapsed group
// would render a toggle that does nothing when clicked. SideNavSection is
// always-expanded and purely static, so it's the only grouping primitive
// ── UserSection ───────────────────────────────────────────────────────────────
// Signed out: a real primary Button (not a bare link) — the sidebar's single
// most important action when there's no session yet.
// a single closed trigger ("Theme: Auto ⌄") that expands upward (the trigger
// sits at the bottom of the sidebar, so the panel opens above it, never off
// -screen). <details> is fully keyboard/screen-reader operable with zero
// client JS — no React hydration exists on this page (see app.tsx), so a
// component that depends on onClick-driven open state (Astryx's own
// ── AppSideNav ────────────────────────────────────────────────────────────────
export function AppSideNav(
{ appName, page, mode, user }: {
return (
<SideNav
header={<SideNavHeading heading={appName} headingHref="/" />}
footer={
{NAV.map((item) => (
<SideNavItem
key={item.page}
{DOCS_SECTIONS.map((section) => (
<SideNavSection key={section.title} title={section.title}>
{section.items.map((item) => (
<SideNavItem
key={item.page}
))}
</SideNavSection>
))}
</SideNav>
);
index.html — HTML shell (loads twind + index.tsx)
index.tsx — React entrypoint, renders <App/>
favicon.svg
lib/auth-client.ts — Better Auth client-side SDK
components/
a sign-in form if not logged in, then the panel.
- **Client-side routing**`App.tsx` has a simple `pushState`/`popstate` router.
No React Router dependency. The Hono catch-all serves `index.html` for all
non-API GET routes so deep links work.
- The `signIn.magicLink` client call needs `callbackURL` set to `/me` (not `/`)
so users land on their registration page after clicking the magic link.
import { createAuthClient } from "https://esm.sh/better-auth@1.6.9/react?deps=react@18.2.0,react
import { magicLinkClient } from "https://esm.sh/better-auth@1.6.9/client/plugins?deps=react@18.2
// Base URL is the same origin as the page, so we don't need to set it.
// Plugins must mirror the server-side plugins enabled in backend/auth.ts.
export const authClient = createAuthClient({
plugins: [magicLinkClient()],
});
export const { useSession, signIn, signOut } = authClient;
- React 18 (client-side)
- Hono (backend API)
index.html ← HTML shell
index.tsx ← React entrypoint
favicon.svg ← Uznik Group icon
vals into collections. Built as a Val Town HTTP val with a React frontend.
- Add / remove vals from collections
- Click a collection in the sidebar to filter the main list to only the vals in that collection
- "Add more vals" section appears when viewing a specific collection (lists vals not yet in it)
- Optimistic UI updates for instant feedback (no full page reloads on add/remove)
- Fully server-rendered initial HTML shell + client-side React for interactivity
- Works with your real private data (no demo mode)
### Creating a Collection
- Use the form in the sidebar (under "COLLECTIONS").
- Type a name and submit. The new collection appears immediately (optimistic) and is auto-select
### Adding / Removing Vals
- Click a collection in the sidebar → the grid filters to only its current members (with "Remove
- Below the filtered list you'll see an "Add more vals" section with the rest of your vals (each
### Tips
- The sidebar always shows live counts.
- Cards link to the actual val on Val Town.
The val follows the standard Val Town `react-hono` layout — real `.tsx` files transpiled to the
- `frontend/index.html` – Static HTML shell. Loads Twind + `std/catch`, then `<script type="modu
- `frontend/index.tsx`Client entry point. Mounts `<App />` into `#root`.
- `frontend/components/`React components: `App.tsx` (state + data + optimistic mutations), `V
- `shared/types.ts` – Types (`ValSummary`, `Collection`) imported by both backend and frontend.
React is loaded from esm.sh pinned to `18.2.0`, and JSX uses the `@jsxImportSource` pragma — so
- Collection rows are filtered by OAuth user ID, so stale or copied rows from another user are n
- Feel free to customize the React UI in `frontend/components/`, add more API routes, etc.
- **Backend**: Val Town + Hono + `std/oauth`. Vals loaded via the official SDK (`me.vals.list`)
- **Frontend**: React 18 (pinned via esm.sh, Twind for styling, zero build step). Real `.tsx` co
- **Why this works per-user**: OAuth provides the current user's access token and user ID. The S
- **Sections** (presentational grouping)
- **Hybrid** (sidebar collections + quick filters with filtering)
These were the original static HTML mocks before the live React version.
## Status
create collections and move vals around with a reasonable React UX) but "not perfect yet" – som
Val Town can't accept **incoming** WebSocket connections, and the Automerge sync
protocol runs over WebSocket. The trick: Val Town only serves a static client, and
the **browser** opens the WebSocket directly to your sync server. This sidesteps the
platform's WebSocket limitation entirely.
- `server.ts` — HTTP handler. Serves `index.html` and transpiles `client.tsx``/client.js`.
- `index.html` — page shell (Twind for styling, mounts the React app).
- `client.tsx` — the whole app: WASM init, repo connection, JSON editor.
3. Either paste an existing document URL (`automerge:...`) and click **Open document**,
or click **New document** to create one. Share the generated URL with another client
to collaborate.
4. Edit the JSON and click **Save changes** — edits merge into the document and sync to
the server. Changes from other clients appear live.
| --- | --- |
ctive web app (dashboard, SaaS, CRUD, form, anything with client-side state) | `templates/react-
| Static page (landing page, link-in-bio, simple docs) | `templates/basic-html-starter` |
c15r/sync/ssr.ts
14 matches
/** @jsxImportSource https://esm.sh/react@18.2.0 */
/** Server-side rendering utility.
*
* Renders a React component to HTML string with styled-components CSS extraction.
* Used by route handlers in main.ts / mcp/auth.ts to produce complete HTML pages.
*/
import { renderToString } from "https://esm.sh/react-dom@18.2.0/server";
StyleSheet } from "https://esm.sh/styled-components@6?deps=react@18.2.0,react-dom@18.2.0";
import type { ReactElement } from "https://esm.sh/react@18.2.0";
import { shell, type ShellOptions } from "./shell.ts";
export interface RenderPageOptions {
/** The React element to render */
element: ReactElement;
/** Path to the client hydration script (e.g. "/frontend/pages/recover/client.tsx") */
entry: string;
/** Props to serialize for client hydration */
props?: Record<string, unknown>;
/** Render a React page to a full HTML Response with SSR + styled-components CSS. */
export function renderPage(opts: RenderPageOptions): Response {