Expand description
Reinhardt Pages - WASM-based Frontend Framework
A Django-inspired frontend framework for Reinhardt that preserves the benefits of Django templates while leveraging WebAssembly for modern interactivity.
Use reactive::batch to group related reactive writes into one update
cycle. Async Action handles can be connected to OptimisticState with
Action::with_optimistic so failed mutations automatically roll back
optimistic UI state.
§Features
- Fine-grained Reactivity: Leptos/Solid.js-style Signal system with React-aligned hooks
- Hybrid Rendering: SSR + Client-side Hydration for optimal performance and SEO
- Django-like API: Familiar patterns for Reinhardt developers
- Boundaries: Suspense and error boundaries for async UI states
§React-aligned hook signatures (v0.2, Refs #4195)
use_effect, use_layout_effect, use_memo, use_callback, and
use_callback_with take an explicit dependency tuple as the second
argument:
use reinhardt_pages::prelude::*;
let count = Signal::new(0_i32);
let count_for_effect = count.clone();
let _eff = use_effect(
move || {
println!("count = {}", count_for_effect.get());
None::<fn()>
},
(count.clone(),),
);Closures run with no active reactive Observer (“Option A”), so
Signal::get inside does NOT auto-subscribe — subscriptions derive
exclusively from the deps tuple. Pass () for mount-only effects.
For a concept-by-concept mapping from React to Reinhardt Pages, see
docs/react_to_reinhardt.md in this crate.
- Low-level Only: Built on wasm-bindgen, web-sys, and js-sys (no high-level framework dependencies)
- Security First: Built-in CSRF protection, XSS prevention, and session management
§Architecture
This framework consists of several key modules:
reactive: Fine-grained reactivity system (Signal, Effect, Memo)dom: DOM abstraction layerbuilder: HTML element builder APIcomponent: Component system with IntoPage trait, Head managementform: Django Form integrationform_state: Typeduse_formruntime statecsrf: CSRF protectionauth: Authentication integrationapi: API client with Django QuerySet-like interfaceserver_fn: Server Functions (RPC)ssr: Server-side rendering with Head supporthydration: Client-side hydrationrouter: Client-side routing (reinhardt-urls compatible)portal: Explicit portal mounting into existing DOM targetsstatic_resolver: Static file URL resolution (collectstatic support)
§Forms
form! owns static form definition: field names, widgets, labels,
rendering metadata, and server function binding. use_form owns typed
runtime behavior: value signals, dirty/touched state, validation errors,
loading/success state, reset, and submit orchestration.
Create a form! generated form and attach runtime behavior with
use_form:
use reinhardt_pages::{form, use_form};
let login_form = form! {
name: LoginForm,
action: "/login",
fields: {
username: CharField { initial: String::new() }
password: CharField { initial: String::new() }
}
};
let runtime = use_form(&login_form).build();
runtime.set_value(login_form.username_field(), "ada".to_string());FileField and ImageField participate in this runtime contract as
Option<web_sys::File> values. File values are browser-owned and are
tracked for dirty/touched state without treating the file payload as a
serializable scalar.
Generated native HTML includes current field values, checkbox state, textarea
content, and selected options. Bound controls synchronize signal updates and
runtime reset through their existing DOM nodes. bind: false renders only
a value snapshot without ongoing synchronization. File inputs remain clear-only for
programmatic updates. Optional temporal/UUID/IP values render empty for
None; datetime-local uses T, and JSON values use compact serialization.
Hydration preserves browser edits and selected files, while explicit runtime
setters and field resets take precedence for their own field or collection path.
Unchanged multiple selections retain source order. Leading textarea line feeds
survive HTML parsing, including selective hydration. Native reset clears touched
state and errors without emitting edit or validation events, and preserves
subscriptions to later custom widget errors and field edits.
Static choice values are evaluated once per option. Textarea hydration compares
normalized HTML line endings so parsing alone does not create an edit.
Unbound textarea snapshots preserve whitespace and validate parsed default text.
Reconciled pristine defaults remain preferred only for their updated fields and paths.
Stable native widget coverage includes the following form! DSL items:
| DSL item | HTML output | Value state |
|---|---|---|
MonthInput | <input type="month"> | string field |
WeekInput | <input type="week"> | string field |
ResetButton | <button type="reset"> | none |
Button | <button type="button"> | none |
ImageInput | <input type="image"> | none |
Datalist | <datalist> | option source only |
OptGroup | <optgroup> | choice grouping only |
Output | <output> | none |
Meter | <meter> | none |
Progress | <progress> | none |
Typed native attributes are accepted for the controls that support them:
| Attribute | Compatible controls |
|---|---|
min / max / step | number, range, date, time, datetime-local, month, week |
size | text-like inputs |
accept / capture | file-like inputs |
multiple | file-like inputs and multi-select |
list | datalist-compatible text-like inputs |
FieldGroup renders as semantic <fieldset> output. When label is
present, the label is rendered as a <legend> inside the fieldset.
Dynamic RadioSelect fields also use <fieldset> / <legend> to name the
radio group. Each option keeps its own label and indexed input ID. Field,
label, and input CSS classes are preserved. Custom wrappers keep their tag and
attributes, with aria-labelledby naming the group from a caption <span>
instead of a legend. The default role is group; an explicit wrapper role is
preserved.
CustomWidget is experimental and must opt in explicitly:
date_range: CharField {
widget: CustomWidget(crate::widgets::DateRangePicker) {
experimental,
adapter: crate::widgets::DateRangeAdapter,
},
}The adapter API may change in a minor release with a documented migration path.
Use ambient_arguments for non-field values supplied from surrounding
context. The old strip_arguments DSL name remains as a deprecated alias.
CSRF should be supplied by #[server_fn] client stubs through the
X-CSRFToken header rather than as a server function business argument.
§Macros
page!: JSX-like macro for defining view componentshead!: JSX-like macro for defining HTML head sectionsform!: Type-safe form component macroclient_page: Client page function macro with native route-table stubswasm_server_api: WASM/server API parity macro
See docs/wasm_server_api.md for the target-specific API parity contract.
§Example
§Basic Component
use reinhardt_pages::{Signal, Page, page};
fn counter() -> Page {
let count = Signal::new(0);
page!(|count: Signal<i32>| {
div {
p { { format!("Count: {}", count.get()) } }
}
})(count)
}§With Head Section
use reinhardt_pages::{head, page, Page, resolve_static};
fn home_page() -> Page {
let page_head = head!(|| {
title { "Home - My App" }
meta { name: "description", content: "Welcome to my app" }
link { rel: "stylesheet", href: resolve_static("css/main.css") }
});
page! {
#head: page_head,
|| {
div { class: "container",
h1 { "Welcome Home" }
}
}
}()
}§WebSocket Integration
The use_websocket hook provides reactive WebSocket connections:
use reinhardt_pages::reactive::hooks::{use_websocket, use_effect, UseWebSocketOptions};
use reinhardt_pages::reactive::hooks::{ConnectionState, WebSocketMessage};
fn chat_component() -> Page {
// Establish WebSocket connection
let ws = use_websocket("ws://localhost:8000/ws/chat", UseWebSocketOptions::default());
// Monitor connection state reactively
use_effect(
{
let ws = ws.clone();
move || {
match ws.connection_state().get() {
ConnectionState::Open => log!("Connected to chat"),
ConnectionState::Closed => log!("Disconnected from chat"),
ConnectionState::Error(e) => log!("Connection error: {}", e),
_ => {}
}
None::<fn()>
}
},
(),
);
// Handle incoming messages
use_effect(
{
let ws = ws.clone();
move || {
if let Some(WebSocketMessage::Text(text)) = ws.latest_message().get() {
log!("Received: {}", text);
}
None::<fn()>
}
},
(),
);
page!(|| {
div {
button {
@click: move |_| {
ws.send_text("Hello, server!".to_string()).ok();
},
"Send Message"
}
}
})()
}Note: WebSocket functionality is WASM-only. On the server side (SSR),
use_websocket returns a no-op handle with connection state always set to Closed.
Re-exports§
pub use api::ApiModel;pub use api::ApiQuerySet;pub use api::Filter;pub use api::FilterOp;pub use auth::AuthData;pub use auth::AuthError;pub use auth::AuthState;pub use auth::auth_state;pub use builder::attributes::AriaAttributes;pub use builder::attributes::BooleanAttributes;pub use builder::html::a;pub use builder::html::div;pub use builder::html::form;pub use builder::html::h1;pub use builder::html::h2;pub use builder::html::h3;pub use builder::html::img;pub use builder::html::input;pub use builder::html::li;pub use builder::html::ol;pub use builder::html::option;pub use builder::html::p;pub use builder::html::select;pub use builder::html::span;pub use builder::html::textarea;pub use builder::html::ul;pub use callback::Callback;pub use callback::IntoEventHandler;pub use callback::event_handler;pub use callback::into_event_handler;pub use component::ActivityBoundary;pub use component::ActivityMode;pub use component::BoundaryError;pub use component::Component;pub use component::ErrorBoundary;pub use component::ErrorTracker;pub use component::PageExt;pub use component::Props;pub use component::ResourceTracker;pub use component::SuspenseBoundary;pub use component::ViewTransitionBoundary;pub use component::ViewTransitionHandle;pub use component::ViewTransitionStatus;pub use component::start_view_transition;pub use csrf::CsrfManager;pub use csrf::get_csrf_token;pub use dom::CustomEventOptions;pub use dom::Document;pub use dom::Element;pub use dom::EventHandle;pub use dom::document;pub use dom::document;pub use form::FormBinding;pub use form::FormComponent;pub use form_generated::StaticFieldMetadata;pub use form_generated::StaticFormMetadata;pub use form_state::CollectionItem;pub use form_state::CollectionItemKey;pub use form_state::CollectionState;pub use form_state::CustomWidgetContext;pub use form_state::CustomWidgetRawValue;pub use form_state::FieldError;pub use form_state::FieldPathState;pub use form_state::FieldState;pub use form_state::FocusError;pub use form_state::FormCollectionRuntimeSource;pub use form_state::FormEvent;pub use form_state::FormRuntimeSource;pub use form_state::FormState;pub use form_state::FormSubscription;pub use form_state::FormValidationError;pub use form_state::FormWidgetAdapter;pub use form_state::FormWidgetError;pub use form_state::FormWidgetValueKind;pub use form_state::NoDeps;pub use form_state::ResetOnDeps;pub use form_state::RevalidateOn;pub use form_state::UseFormBuilder;pub use form_state::UseFormReturn;pub use form_state::UseFormSubmitOutcome;pub use form_state::use_form;pub use hydration::HydrationContext;pub use hydration::HydrationError;pub use hydration::hydrate;pub use portal::Portal;pub use portal::PortalError;pub use portal::PortalHandle;pub use portal::PortalTarget;pub use portal::mount_portal;pub use reactive::Resource;pub use reactive::ResourceState;pub use reactive::use_resource;pub use app::ClientLauncher;pub use app::LaunchCtx;pub use app::PathCtx;pub use app::PathParams;pub use reactive::Action;pub use reactive::ActionPhase;pub use reactive::use_action;pub use reactive::Dispatch;pub use reactive::OptimisticState;pub use reactive::Ref;pub use reactive::SetState;pub use reactive::TransitionState;pub use reactive::use_callback;pub use reactive::use_context;pub use reactive::use_debug_value;pub use reactive::use_deferred_value;pub use reactive::use_effect;pub use reactive::use_id;pub use reactive::use_layout_effect;pub use reactive::use_memo;pub use reactive::use_optimistic;pub use reactive::use_reducer;pub use reactive::use_ref;pub use reactive::use_state;pub use reactive::use_sync_external_store;pub use reactive::use_transition;pub use router::Link;pub use reactive::hooks::router::RouterHandle;pub use reactive::hooks::router::use_router;pub use router::Query;pub use server_fn::ServerFn;pub use server_fn::ServerFnError;pub use server_fn::parse_server_error_message;pub use ssr::SsrState;pub use ssr::SsrOptions;pub use ssr::SsrRenderer;pub use static_resolver::init_static_resolver;pub use static_resolver::is_initialized;pub use static_resolver::resolve_static;pub use reinhardt_pages_ast as ast;Deprecated
Modules§
- api
- API Client with Django QuerySet-like Interface
- app
- WASM client application launcher.
- auth
- Authentication State Management for Client-side WASM
- builder
- HTML Builder API
- callback
- Callback types and event handler conversion traits.
- component
- Component System for reinhardt-pages
- csrf
- CSRF Protection for Client-side WASM
- dom
- DOM Abstraction Layer
- form
- Form Integration for Reinhardt WASM (Week 5 Day 3-4)
- form_
generated - Static Metadata Types for form! Macro Generated Code
- form_
state - Runtime behavior for
form!generated forms. - hydration
- Hydration Module for reinhardt-pages
- integ
- Integration modules for special macros.
- logging
- Logging abstraction layer for reinhardt-pages
- platform
- Platform abstraction for WASM and native targets.
- portal
- Explicit portal mounting for Reinhardt Pages.
- prelude
- Unified prelude for reinhardt-pages.
- reactive
- Reactive System
- router
- Client-Side Router for reinhardt-pages
- server_
fn - Server Functions (RPC)
- spawn
- Backward-compatibility re-export of task-spawning utilities.
- ssr
- Server-Side Rendering (SSR) Infrastructure
- static_
resolver - Static File URL Resolver
- tables
- Table utilities for reinhardt-pages (django-tables2 equivalent)
Macros§
- debug_
log - No-op debug_log when conditions are not met
- error_
log - Logs an error message (requires
debug_assertions) - form
- Form component macro
- head
- Head section macro
- info_
log - Logs an info message (requires
debug_assertions) - nav_
diag - No-op
nav_diagoutside WASM dev builds. - nav_
diag_ dom - No-op
nav_diag_domwhen thenav-diag-domfeature is disabled or on non-WASM targets. - page
- Derives typed field signals and form traits for a form values struct. Page component macro
- warn_
log - Logs a warning message (requires
debug_assertions)
Structs§
- Context
- A type-safe context identifier.
- Context
Guard - A RAII guard that removes a context value when dropped.
- Dummy
Event - Dummy event type for non-WASM environments.
- Effect
- A reactive effect that automatically re-runs when its dependencies change
- Field
Metadata - Serializable field metadata for client-side rendering (Week 5 Day 1)
- Form
Metadata - Serializable form metadata for client-side rendering (Week 5 Day 1)
- Head
- Represents the complete HTML
<head>section. - LinkTag
- Represents an HTML
<link>tag. - Memo
- A memoized reactive computation that caches its result
- MetaTag
- Represents an HTML
<meta>tag. - Page
Element - Represents a DOM element in the view tree.
- Path
- Single path parameter extractor.
- Script
Tag - Represents an HTML
<script>tag. - Signal
- A reactive signal that holds a value and tracks dependencies
- Style
Tag - Represents an HTML
<style>tag with inline CSS.
Enums§
- Event
Type - Common DOM event types
- Navigation
Type - The type of navigation that occurred.
- Page
- A unified representation of renderable content.
- Widget
- Field widget type that determines HTML rendering.
Traits§
- Into
Page - Trait for types that can be converted into a Page.
Functions§
- create_
context - Creates a new context.
- get_
context - Gets the current value from a context.
- provide_
context - Provides a value for a context.
- remove_
context - Removes the most recently provided value for a context.
Attribute Macros§
- client_
page - Declares a client page function with a native route-table stub.
- component
- Declares a route-backed page component.
- page_
props - Adds builder support and
FromRequestextraction to a named props struct. - wasm_
server_ api - Declares public APIs with matching WASM and server-side surfaces.
Derive Macros§
- From
Request - Derives
FromRequestfor named props structs with extractor fields.