Skip to main content

Crate reinhardt_pages

Crate reinhardt_pages 

Source
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 layer
  • builder: HTML element builder API
  • component: Component system with IntoPage trait, Head management
  • form: Django Form integration
  • form_state: Typed use_form runtime state
  • csrf: CSRF protection
  • auth: Authentication integration
  • api: API client with Django QuerySet-like interface
  • server_fn: Server Functions (RPC)
  • ssr: Server-side rendering with Head support
  • hydration: Client-side hydration
  • router: Client-side routing (reinhardt-urls compatible)
  • portal: Explicit portal mounting into existing DOM targets
  • static_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 itemHTML outputValue 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:

AttributeCompatible controls
min / max / stepnumber, range, date, time, datetime-local, month, week
sizetext-like inputs
accept / capturefile-like inputs
multiplefile-like inputs and multi-select
listdatalist-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 components
  • head!: JSX-like macro for defining HTML head sections
  • form!: Type-safe form component macro
  • client_page: Client page function macro with native route-table stubs
  • wasm_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::button;
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::SharedSetState;
pub use reactive::SharedSignal;
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_shared_state;
pub use reactive::use_state;
pub use reactive::use_sync_external_store;
pub use reactive::use_transition;
pub use reactive::hooks::router::NavigateError;
pub use reactive::hooks::router::RouterHandle;
pub use reactive::hooks::router::use_router;
pub use router::navigate;
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_diag outside WASM dev builds.
nav_diag_dom
No-op nav_diag_dom when the nav-diag-dom feature 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.
ContextGuard
A RAII guard that removes a context value when dropped.
DummyEvent
Dummy event type for non-WASM environments.
Effect
A reactive effect that automatically re-runs when its dependencies change
FieldMetadata
Serializable field metadata for client-side rendering (Week 5 Day 1)
FormMetadata
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.
PageElement
Represents a DOM element in the view tree.
Path
Single path parameter extractor.
ScriptTag
Represents an HTML <script> tag.
Signal
A reactive signal that holds a value and tracks dependencies
StyleTag
Represents an HTML <style> tag with inline CSS.

Enums§

EventType
Common DOM event types
NavigationType
The type of navigation that occurred.
Page
A unified representation of renderable content.
Widget
Field widget type that determines HTML rendering.

Traits§

IntoPage
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 FromRequest extraction to a named props struct.
wasm_server_api
Declares public APIs with matching WASM and server-side surfaces.

Derive Macros§

FromRequest
Derives FromRequest for named props structs with extractor fields.