Crate leptos_router
source ·Expand description
Leptos Router
Leptos Router is a router and state management tool for web applications written in Rust using the Leptos web framework. It is ”isomorphic,” i.e., it can be used for client-side applications/single-page apps (SPAs), server-side rendering/multi-page apps (MPAs), or to synchronize state between the two.
Note: This is a work in progress. The feature to pass client-side route State in History.state, in particular, is incomplete.
Philosophy
Leptos Router is built on a few simple principles:
-
URL drives state. For web applications, the URL should be the ultimate source of truth for most of your app’s state. (It’s called a Universal Resource Locator for a reason!)
-
Nested routing. A URL can match multiple routes that exist in a nested tree and are rendered by different components. This means you can navigate between siblings in this tree without re-rendering or triggering any change in the parent routes.
-
Route-based data loading. Each route should know exactly which data it needs to render itself when the route is defined. This allows each route’s data to be reloaded independently, and allows data from nested routes to be loaded in parallel, avoiding waterfalls.
-
Progressive enhancement. The A and Form components resolve any relative nested routes, render actual
<a>
and<form>
elements, and (when possible) upgrading them to handle those navigations with client-side routing. If you’re using them with server-side rendering (with or without hydration), they just work, whether JS/WASM have loaded or not.
Example
use leptos::*;
use leptos_router::*;
#[component]
pub fn RouterExample(cx: Scope) -> impl IntoView {
view! {
cx,
<div id="root">
// we wrap the whole app in a <Router/> to allow client-side navigation
// from our nav links below
<Router>
// <nav> and <main> will show on every route
<nav>
// LR will enhance the active <a> link with the [aria-current] attribute
// we can use this for styling them with CSS like `[aria-current] { font-weight: bold; }`
<A href="contacts">"Contacts"</A>
<A href="about">"About"</A>
<A href="settings">"Settings"</A>
</nav>
<main>
// <Routes/> both defines our routes and shows them on the page
<Routes>
// our root route: the contact list is always shown
<Route
path=""
view=move |cx| view! { cx, <ContactList/> }
>
// users like /gbj or /bob
<Route
path=":id"
view=move |cx| view! { cx, <Contact/> }
/>
// a fallback if the /:id segment is missing from the URL
<Route
path=""
view=move |_| view! { cx, <p class="contact">"Select a contact."</p> }
/>
</Route>
// LR will automatically use this for /about, not the /:id match above
<Route
path="about"
view=move |cx| view! { cx, <About/> }
/>
</Routes>
</main>
</Router>
</div>
}
}
type ContactSummary = (); // TODO!
type Contact = (); // TODO!()
// contact_data reruns whenever the :id param changes
async fn contact_data(id: String) -> Contact {
todo!()
}
// contact_list_data *doesn't* rerun when the :id changes,
// because that param is nested lower than the <ContactList/> route
async fn contact_list_data() -> Vec<ContactSummary> {
todo!()
}
#[component]
fn ContactList(cx: Scope) -> impl IntoView {
// loads the contact list data once; doesn't reload when nested routes change
let contacts = create_resource(cx, || (), |_| contact_list_data());
view! {
cx,
<div>
// show the contacts
<ul>
{move || contacts.read().map(|contacts| view! { cx, <li>"todo contact info"</li> } )}
</ul>
// insert the nested child route here
<Outlet/>
</div>
}
}
#[component]
fn Contact(cx: Scope) -> impl IntoView {
let params = use_params_map(cx);
let data = create_resource(
cx,
move || params.with(|p| p.get("id").cloned().unwrap_or_default()),
move |id| contact_data(id)
);
todo!()
}
#[component]
fn About(cx: Scope) -> impl IntoView {
todo!()
}
Module Route Definitions
Routes can also be modularized and nested by defining them in separate components, which can be
located in and imported from other modules. Components that return <Route/>
should be marked
#[component(transparent)]
, as in this example:
use leptos::*;
use leptos_router::*;
#[component]
pub fn App(cx: Scope) -> impl IntoView {
view! { cx,
<Router>
<Routes>
<Route path="/" view=move |cx| {
view! { cx, "-> /" }
}/>
<ExternallyDefinedRoute/>
</Routes>
</Router>
}
}
// `transparent` here marks the component as returning data (a RouteDefinition), not a view
#[component(transparent)]
pub fn ExternallyDefinedRoute(cx: Scope) -> impl IntoView {
view! { cx,
<Route path="/some-area" view=move |cx| {
view! { cx, <div>
<h2>"Some Area"</h2>
<Outlet/>
</div> }
}>
<Route path="/path-a/:id" view=move |cx| {
view! { cx, <p>"Path A"</p> }
}/>
<Route path="/path-b/:id" view=move |cx| {
view! { cx, <p>"Path B"</p> }
}/>
</Route>
}
}
Feature Flags
csr
Client-side rendering: Generate DOM nodes in the browserssr
Server-side rendering: Generate an HTML string (typically on the server)hydrate
Hydration: use this to add interactivity to an SSRed Leptos appstable
By default, Leptos requiresnightly
Rust, which is what allows the ergonomics of calling signals as functions. Enable this feature to supportstable
Rust.
Important Note: You must enable one of csr
, hydrate
, or ssr
to tell Leptos
which mode your app is operating in.
Re-exports
pub use matching::*;
Macros
Structs
A
] component.AProps
instances.ActionForm
component.ActionFormProps
instances.History API
.Form
] component.FormProps
instances.Location
.MultiActionForm
component.MultiActionFormProps
instances.Outlet
component.OutletProps
instances.Route
component.RouteProps
instances.Router
component.RouterProps
instances.Routes
component.RoutesProps
instances.Enums
Traits
Self
should typically be a struct in which
each field’s type implements FromStr.Functions
form
progressively enhanced to use client-side routing.generate_route_list()
from either the actix or axum integrations if you want
to work with their router