mirror of
https://github.com/DioxusLabs/dioxus
synced 2025-02-18 14:48:26 +00:00
finish router refrence
This commit is contained in:
parent
07446386e6
commit
b5a2f0d6cb
61 changed files with 676 additions and 1908 deletions
0
blog.html
Normal file
0
blog.html
Normal file
24
docs/router/examples/catch_all_segments.rs
Normal file
24
docs/router/examples/catch_all_segments.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: route
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// segments that start with :... are catch all segments
|
||||||
|
#[route("/blog/:...segments")]
|
||||||
|
BlogPost {
|
||||||
|
// You must include catch all segment in child variants
|
||||||
|
segments: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Components must contain the same catch all segments as their corresponding variant
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, segments: Vec<String>) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// ANCHOR_END: route
|
||||||
|
|
||||||
|
fn main() {}
|
35
docs/router/examples/dynamic_segments.rs
Normal file
35
docs/router/examples/dynamic_segments.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: route
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// segments that start with : are dynamic segments
|
||||||
|
#[route("/blog/:name")]
|
||||||
|
BlogPost {
|
||||||
|
// You must include dynamic segments in child variants
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
#[route("/document/:id")]
|
||||||
|
Document {
|
||||||
|
// You can use any type that implements FromStr
|
||||||
|
// If the segment can't be parsed, the route will not match
|
||||||
|
id: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Components must contain the same dynamic segments as their corresponding variant
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, name: String) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Document(cx: Scope, id: usize) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// ANCHOR_END: route
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -9,6 +9,7 @@ enum Route {
|
||||||
// The home page is at the / route
|
// The home page is at the / route
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
// If the name of the component and variant are the same you can omit the component and props name
|
// If the name of the component and variant are the same you can omit the component and props name
|
||||||
|
// If they are different you can specify them like this:
|
||||||
// #[route("/", ComponentName, PropsName)]
|
// #[route("/", ComponentName, PropsName)]
|
||||||
Home {},
|
Home {},
|
||||||
}
|
}
|
||||||
|
|
30
docs/router/examples/history_buttons.rs
Normal file
30
docs/router/examples/history_buttons.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR: history_buttons
|
||||||
|
fn HistoryNavigation(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
GoBackButton {
|
||||||
|
"Back to the Past"
|
||||||
|
}
|
||||||
|
GoForwardButton {
|
||||||
|
"Back to the Future" /* You see what I did there? 😉 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: history_buttons
|
||||||
|
|
||||||
|
fn main() {}
|
29
docs/router/examples/history_provider.rs
Normal file
29
docs/router/examples/history_provider.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
enum Route {
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR: app
|
||||||
|
#[inline_props]
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {
|
||||||
|
config: || RouterConfig::default().history(WebHistory::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: app
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
56
docs/router/examples/navigator.rs
Normal file
56
docs/router/examples/navigator.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
#[route("/:...route")]
|
||||||
|
PageNotFound { route: Vec<String> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCHOR: nav
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
let nav = use_navigator(cx);
|
||||||
|
|
||||||
|
// push
|
||||||
|
nav.push(Route::PageNotFound { route: vec![] });
|
||||||
|
|
||||||
|
// replace
|
||||||
|
nav.replace(Route::Home {});
|
||||||
|
|
||||||
|
// go back
|
||||||
|
nav.go_back();
|
||||||
|
|
||||||
|
// go forward
|
||||||
|
nav.go_forward();
|
||||||
|
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: nav
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Page not found" }
|
||||||
|
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
||||||
|
pre {
|
||||||
|
color: "red",
|
||||||
|
"log:\nattemped to navigate to: {route:?}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
40
docs/router/examples/nest.rs
Normal file
40
docs/router/examples/nest.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: route
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
// Skipping formatting allows you to indent nests
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// Start the /blog nest
|
||||||
|
#[nest("/blog")]
|
||||||
|
// You can nest as many times as you want
|
||||||
|
#[nest("/:id")]
|
||||||
|
#[route("/post")]
|
||||||
|
PostId {
|
||||||
|
// You must include parent dynamic segments in child variants
|
||||||
|
id: usize,
|
||||||
|
},
|
||||||
|
// End nests manually with #[end_nest]
|
||||||
|
#[end_nest]
|
||||||
|
#[route("/:id")]
|
||||||
|
// The absolute route of BlogPost is /blog/:name
|
||||||
|
BlogPost {
|
||||||
|
id: usize,
|
||||||
|
},
|
||||||
|
// Or nests are ended automatically at the end of the enum
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, id: usize) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn PostId(cx: Scope, id: usize) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// ANCHOR_END: route
|
||||||
|
|
||||||
|
fn main() {}
|
46
docs/router/examples/outlet.rs
Normal file
46
docs/router/examples/outlet.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: outlet
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
#[layout(Wrapper)]
|
||||||
|
#[route("/")]
|
||||||
|
Index {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Wrapper(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
header { "header" }
|
||||||
|
// The index route will be rendered here
|
||||||
|
Outlet { }
|
||||||
|
footer { "footer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Index(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Index" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: outlet
|
||||||
|
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut vdom = VirtualDom::new(App);
|
||||||
|
let _ = vdom.rebuild();
|
||||||
|
let html = dioxus_ssr::render(&vdom);
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"<header>header</header><h1>Index</h1><footer>footer</footer>"
|
||||||
|
);
|
||||||
|
}
|
24
docs/router/examples/query_segments.rs
Normal file
24
docs/router/examples/query_segments.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: route
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// segments that start with ?: are query segments
|
||||||
|
#[route("/blog?:name")]
|
||||||
|
BlogPost {
|
||||||
|
// You must include query segments in child variants
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Components must contain the same query segments as their corresponding variant
|
||||||
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, name: String) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// ANCHOR_END: route
|
||||||
|
|
||||||
|
fn main() {}
|
37
docs/router/examples/router_cfg.rs
Normal file
37
docs/router/examples/router_cfg.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// ANCHOR: router
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
/// An enum of all of the possible routes in the app.
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
enum Route {
|
||||||
|
// The home page is at the / route
|
||||||
|
#[route("/")]
|
||||||
|
// If the name of the component and variant are the same you can omit the component and props name
|
||||||
|
// #[route("/", ComponentName, PropsName)]
|
||||||
|
Home {},
|
||||||
|
}
|
||||||
|
// ANCHOR_END: router
|
||||||
|
|
||||||
|
// ANCHOR: app
|
||||||
|
#[inline_props]
|
||||||
|
fn App(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {
|
||||||
|
config: || RouterConfig::default().history(WebHistory::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: app
|
||||||
|
|
||||||
|
// ANCHOR: home
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
h1 { "Welcome to the Dioxus Blog!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: home
|
||||||
|
|
||||||
|
fn main() {}
|
41
docs/router/examples/routing_update.rs
Normal file
41
docs/router/examples/routing_update.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#![allow(non_snake_case, unused)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: router
|
||||||
|
#[derive(Routable, Clone, PartialEq)]
|
||||||
|
enum Route {
|
||||||
|
#[route("/")]
|
||||||
|
Index {},
|
||||||
|
#[route("/home")]
|
||||||
|
Home {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
p { "Home" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Index(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
p { "Index" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
Router {
|
||||||
|
config: || RouterConfig::default().on_update(|state|{
|
||||||
|
(state.current() == Route::Index {}).then(|| {
|
||||||
|
NavigationTarget::Internal(Route::Home {})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ANCHOR_END: router
|
||||||
|
|
||||||
|
fn main() {}
|
29
docs/router/examples/static_segments.rs
Normal file
29
docs/router/examples/static_segments.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
|
||||||
|
// ANCHOR: route
|
||||||
|
#[derive(Routable, Clone)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// Routes always start with a slash
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
// You can have multiple segments in a route
|
||||||
|
#[route("/hello/world")]
|
||||||
|
HelloWorld {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn HelloWorld(cx: Scope) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// ANCHOR_END: route
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -10,3 +10,16 @@
|
||||||
- [Navigation Targets](./example/navigation-targets.md)
|
- [Navigation Targets](./example/navigation-targets.md)
|
||||||
- [Redirection Perfection](./example/redirection-perfection.md)
|
- [Redirection Perfection](./example/redirection-perfection.md)
|
||||||
- [Full Code](./example/full-code.md)
|
- [Full Code](./example/full-code.md)
|
||||||
|
|
||||||
|
# Reference
|
||||||
|
|
||||||
|
- [Adding the Router to Your Application](./reference/index.md)
|
||||||
|
- [Defining Routes](./reference/routes/index.md)
|
||||||
|
- [Nested Routes](./reference/routes/nested.md)
|
||||||
|
- [Layouts](./reference/layouts.md)
|
||||||
|
- [Navigation](./reference/navigation/index.md)
|
||||||
|
- [Programmatic Navigation](./reference/navigation/programmatic.md)
|
||||||
|
- [History Providers](./reference/history-providers.md)
|
||||||
|
- [History Buttons](./reference/history-buttons.md)
|
||||||
|
- [Static Generation](./reference/static-generation.md)
|
||||||
|
- [Routing Update Callback](./reference/routing-update-callback.md)
|
||||||
|
|
|
@ -13,7 +13,7 @@ First, we need an actual page to route to! Let's add a homepage component:
|
||||||
{{#include ../../examples/first_route.rs:home}}
|
{{#include ../../examples/first_route.rs:home}}
|
||||||
```
|
```
|
||||||
|
|
||||||
## To Route or Not to Route
|
## Creating Routes
|
||||||
|
|
||||||
We want to use Dioxus Router to separate our application into different "pages".
|
We want to use Dioxus Router to separate our application into different "pages".
|
||||||
Dioxus Router will then determine which page to render based on the URL path.
|
Dioxus Router will then determine which page to render based on the URL path.
|
||||||
|
@ -35,7 +35,7 @@ you enter a different path for the URL, nothing should be displayed.
|
||||||
This is because we told Dioxus Router to render the `Home` component only when
|
This is because we told Dioxus Router to render the `Home` component only when
|
||||||
the URL path is `/`.
|
the URL path is `/`.
|
||||||
|
|
||||||
## What if a Route Doesn't Exist?
|
## Fallback Route
|
||||||
|
|
||||||
In our example, when a route doesn't exist Dioxus Router doesn't render anything. Many sites also have a "404" page when a path does not exist. Let's add one to our site.
|
In our example, when a route doesn't exist Dioxus Router doesn't render anything. Many sites also have a "404" page when a path does not exist. Let's add one to our site.
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,6 @@ If we need a link to an external page we can do it like this:
|
||||||
{{#include ../../examples/external_link.rs:component}}
|
{{#include ../../examples/external_link.rs:component}}
|
||||||
```
|
```
|
||||||
|
|
||||||
[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External
|
[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.External
|
||||||
[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal
|
[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.Internal
|
||||||
[`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html
|
[`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
# External Navigation Failure
|
|
|
@ -1 +0,0 @@
|
||||||
# Navigation Failures
|
|
|
@ -1 +0,0 @@
|
||||||
# Named Navigation Failure
|
|
|
@ -1 +0,0 @@
|
||||||
# Redirection Limit Failure
|
|
|
@ -1 +0,0 @@
|
||||||
# History Buttons
|
|
|
@ -1 +0,0 @@
|
||||||
# History Providers
|
|
|
@ -1 +0,0 @@
|
||||||
# Adding the Router to Your Application
|
|
|
@ -1 +0,0 @@
|
||||||
# External Navigation
|
|
|
@ -1 +0,0 @@
|
||||||
# Links & Navigation
|
|
|
@ -1 +0,0 @@
|
||||||
# Named Navigation
|
|
|
@ -1 +0,0 @@
|
||||||
# Programmatic Navigation
|
|
|
@ -1 +0,0 @@
|
||||||
# Outlets
|
|
|
@ -1 +0,0 @@
|
||||||
# Query
|
|
|
@ -1 +0,0 @@
|
||||||
# Catch All Routes
|
|
|
@ -1 +0,0 @@
|
||||||
# Fallback Routes (404 page)
|
|
|
@ -1 +0,0 @@
|
||||||
# Defining Routes
|
|
|
@ -1 +0,0 @@
|
||||||
# Matching Routes
|
|
|
@ -1 +0,0 @@
|
||||||
# Multiple Components & Redirects
|
|
|
@ -1 +0,0 @@
|
||||||
# Nested Routes
|
|
|
@ -1 +0,0 @@
|
||||||
# Routing Update Callback
|
|
|
@ -1 +0,0 @@
|
||||||
# Sitemap Generation
|
|
|
@ -15,12 +15,12 @@ cargo add dioxus-router
|
||||||
This book is intended to get you up to speed with Dioxus Router. It is split
|
This book is intended to get you up to speed with Dioxus Router. It is split
|
||||||
into two sections:
|
into two sections:
|
||||||
|
|
||||||
1. The [Reference](./reference/index.md) part explains individual features in
|
1. The [reference](./reference/index.md) section explains individual features in
|
||||||
depth. You can read it start to finish, or you can read individual chapters
|
depth. You can read it from start to finish, or you can read individual chapters
|
||||||
in whatever order you want.
|
in whatever order you want.
|
||||||
2. If you prefer a learning-by-doing approach, you can check out the
|
2. If you prefer a learning-by-doing approach, you can check out the
|
||||||
_[example project](./example/index.md)_. It guides you through
|
_[example project](./example/index.md)_. It guides you through
|
||||||
creating a dioxus app, setting up the router and using some of its
|
creating a dioxus app, setting up the router, and using some of its
|
||||||
functionality.
|
functionality.
|
||||||
|
|
||||||
> Please note that this is not the only documentation for the Dioxus Router. You
|
> Please note that this is not the only documentation for the Dioxus Router. You
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
# Navigation Failures
|
|
||||||
|
|
||||||
Some specific operations can cause a failure within router operations. The
|
|
||||||
subchapters contain information on how the router lets us handle such failures.
|
|
||||||
|
|
||||||
# External Navigation Failure
|
|
||||||
|
|
||||||
> This section doesn't apply when specifying a `target` on a [`Link`]. See the
|
|
||||||
> chapter about [external navigation](../navigation/external.md) for more
|
|
||||||
> details.
|
|
||||||
|
|
||||||
When we ask the router to navigate to an external target, either through
|
|
||||||
[programmatic navigation](../navigation/programmatic.md) or a
|
|
||||||
[redirect](../routes/multiple-and-redirect.md#redirects) the router needs to
|
|
||||||
navigate to an external target without being able to rely on an anchor element.
|
|
||||||
|
|
||||||
This will only work in the browser, when using either [`WebHistory`] or
|
|
||||||
[`WebHashHistory`].
|
|
||||||
|
|
||||||
## Failure handling
|
|
||||||
|
|
||||||
When the router encounters an external navigation it cannot fulfill, it changes
|
|
||||||
the path to `/` and shows some fallback content.
|
|
||||||
|
|
||||||
> You can detect if the router is in the external navigation failure handling
|
|
||||||
> state by [checking](../navigation/name.md#check-if-a-name-is-present) if the
|
|
||||||
> [`FailureExternalNavigation`] name is present.
|
|
||||||
|
|
||||||
The default fallback explains to the user that the navigation was unsuccessful
|
|
||||||
and provides them with a [`Link`] to fulfill it manually. It also allows them to
|
|
||||||
go back to the previous page.
|
|
||||||
|
|
||||||
You can override it by setting the `failure_external_navigation` value of the
|
|
||||||
[`RouterConfiguration`]. The external URL will be provided via the
|
|
||||||
[`FailureExternalNavigation`] parameter.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
fn ExternalNavigationFallback(cx: Scope) -> Element {
|
|
||||||
let route = use_route(cx).expect("is nested within a Router component");
|
|
||||||
let url = route
|
|
||||||
.parameter::<FailureExternalNavigation>()
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
render! {
|
|
||||||
h1 { "External navigation failure!" }
|
|
||||||
Link {
|
|
||||||
target: url,
|
|
||||||
"Go to external site"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
failure_external_navigation: comp(ExternalNavigationFallback),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::empty()
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[`FailureExternalNavigation`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.FailureExternalNavigation.html
|
|
||||||
[`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html
|
|
||||||
[`RouterConfiguration`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/struct.RouterConfiguration.html
|
|
||||||
[`WebHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html
|
|
||||||
[`WebHashHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHashHistory.html
|
|
|
@ -1,11 +1,11 @@
|
||||||
# History Buttons
|
# History Buttons
|
||||||
|
|
||||||
Some platforms, like web browsers, provide users with an easy way to navigate
|
Some platforms, like web browsers, provide users with an easy way to navigate
|
||||||
through an apps history. They have UI elements or integrate with the OS.
|
through an app's history. They have UI elements or integrate with the OS.
|
||||||
|
|
||||||
However, native platforms usually don't provide such amenities, which means that
|
However, native platforms usually don't provide such amenities, which means that
|
||||||
apps wanting users to have access to them, need to implement them. For this
|
apps wanting users to have access to them, need to implement them. For this
|
||||||
reason the router comes with two components, which emulate a browsers back and
|
reason, the router comes with two components, which emulate a browser's back and
|
||||||
forward buttons:
|
forward buttons:
|
||||||
|
|
||||||
- [`GoBackButton`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoBackButton.html)
|
- [`GoBackButton`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoBackButton.html)
|
||||||
|
@ -14,38 +14,19 @@ forward buttons:
|
||||||
> If you want to navigate through the history programmatically, take a look at
|
> If you want to navigate through the history programmatically, take a look at
|
||||||
> [`programmatic navigation`](./navigation/programmatic.md).
|
> [`programmatic navigation`](./navigation/programmatic.md).
|
||||||
|
|
||||||
```rust, no_run, no_run
|
```rust, no_run
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
{{#include ../../examples/history_buttons.rs:history_buttons}}
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::prelude::*;
|
|
||||||
|
|
||||||
fn HistoryNavigation(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
GoBackButton {
|
|
||||||
"Back to the Past"
|
|
||||||
}
|
|
||||||
GoForwardButton {
|
|
||||||
"Back to the Future" /* You see what I did there? 😉 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
As you might know, browsers usually disable the back and forward buttons if
|
As you might know, browsers usually disable the back and forward buttons if
|
||||||
there is no history to navigate to. The routers history buttons try to do that
|
there is no history to navigate to. The router's history buttons try to do that
|
||||||
too, but depending on the [history provider] that might not be possible.
|
too, but depending on the [history provider] that might not be possible.
|
||||||
|
|
||||||
Importantly, neither [`WebHistory`] nor [`WebHashHistory`] support that feature.
|
Importantly, neither [`WebHistory`] supports that feature.
|
||||||
This is due to limitations of the browser History API.
|
This is due to limitations of the browser History API.
|
||||||
|
|
||||||
However, in both cases the router will just ignore button presses, if there is
|
However, in both cases, the router will just ignore button presses, if there is
|
||||||
no history to navigate to.
|
no history to navigate to.
|
||||||
|
|
||||||
Also, when using [`WebHistory`] or [`WebHashHistory`], the history buttons might
|
Also, when using [`WebHistory`], the history buttons might
|
||||||
navigate a user to a history entry outside your app.
|
navigate a user to a history entry outside your app.
|
||||||
|
|
||||||
[history provider]: ./history-providers.md
|
|
||||||
[`WebHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html
|
|
||||||
[`WebHashHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHashHistory.html
|
|
||||||
|
|
|
@ -1,43 +1,20 @@
|
||||||
# History Providers
|
# History Providers
|
||||||
|
|
||||||
In order to provide the ability to traverse the navigation history, the router
|
[`HistoryProvider`]s are used by the router to keep track of the navigation history
|
||||||
uses [`HistoryProvider`]s. Those implement the actual back-and-forth
|
and update any external state (e.g. the browser's URL).
|
||||||
functionality.
|
|
||||||
|
|
||||||
The router provides five [`HistoryProvider`]s, but you can also create your own.
|
The router provides five [`HistoryProvider`]s, but you can also create your own.
|
||||||
The five default implementations are:
|
The five default implementations are:
|
||||||
|
|
||||||
- The [`MemoryHistory`] is a custom implementation that works in memory.
|
- The [`MemoryHistory`] is a custom implementation that works in memory.
|
||||||
- The [`WebHistory`] integrates with the browsers URL.
|
- The [`WebHistory`] integrates with the browser's URL.
|
||||||
|
|
||||||
By default the router uses the [`MemoryHistory`]. It might be changed to use
|
By default, the router uses the [`MemoryHistory`]. It might be changed to use
|
||||||
[`WebHistory`] when the `web` feature is active, but that is not guaranteed.
|
[`WebHistory`] when the `web` feature is active, but that is not guaranteed.
|
||||||
|
|
||||||
You can override the default history:
|
You can override the default history:
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
{{#include ../../examples/history_provider.rs:app}}
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::{prelude::*, history::WebHashHistory};
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
history: Box::new(WebHashHistory::new(true)),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::empty()
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
[`HistoryProvider`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/trait.HistoryProvider.html
|
|
||||||
[`MemoryHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.MemoryHistory.html
|
|
||||||
[`WebHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html
|
|
||||||
|
|
|
@ -1,54 +1,23 @@
|
||||||
# Adding the Router to Your Application
|
# Adding the Router to Your Application
|
||||||
|
|
||||||
In this chapter we will learn how to add the router to our app. By it self, this
|
In this chapter, we will learn how to add the router to our app. By itself, this
|
||||||
is not very useful. However, it is a prerequisite for all the functionality
|
is not very useful. However, it is a prerequisite for all the functionality
|
||||||
described in the other chapters.
|
described in the other chapters.
|
||||||
|
|
||||||
> Make sure you added the `dioxus-router` dependency as explained in the
|
> Make sure you added the `dioxus-router` dependency as explained in the
|
||||||
> [introduction](../index.md).
|
> [introduction](../index.md).
|
||||||
|
|
||||||
In most cases we want to add the router to the root component of our app. This
|
In most cases, we want to add the router to the root component of our app. This
|
||||||
way, we can ensure that we have access to all its functionality everywhere. We
|
way, we can ensure that we have access to all its functionality everywhere.
|
||||||
add it by using the [`use_router`] hook
|
|
||||||
|
First, we define the router with the router macro:
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
{{#include ../../examples/first_route.rs:router}}
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::prelude::*;
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
|
|
||||||
// This is the component we pass to dioxus when launching our app.
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
// Here we add the router. All components inside `App` have access to its
|
|
||||||
// functionality.
|
|
||||||
let routes = use_router(
|
|
||||||
cx,
|
|
||||||
// The router can be configured with this parameter.
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
// This tells the router about all the routes in our application. As we
|
|
||||||
// don't have any, we pass an empty segment
|
|
||||||
&|| Segment::empty()
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
h1 { "Our sites title" }
|
|
||||||
|
|
||||||
// The Outlet tells the Router where to render active content.
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# let _ = vdom.rebuild();
|
|
||||||
# assert_eq!(
|
|
||||||
# dioxus_ssr::render(&vdom),
|
|
||||||
# "<h1>Our sites title</h1>"
|
|
||||||
# );
|
|
||||||
```
|
```
|
||||||
|
|
||||||
[`use_router`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html
|
Then we render the router with the [`Router`] component.
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/first_route.rs:app}}
|
||||||
|
```
|
||||||
|
|
19
docs/router/src/reference/layouts.md
Normal file
19
docs/router/src/reference/layouts.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Layouts
|
||||||
|
|
||||||
|
Layouts allow you to
|
||||||
|
|
||||||
|
[`Outlet`]s tell the router where to render content in layouts. In the following example,
|
||||||
|
the Index will be rendered within the [`Outlet`].
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/outlet.rs:outlet}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The example above will output the following HTML (line breaks added for
|
||||||
|
readability):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<header>header</header>
|
||||||
|
<h1>Index</h1>
|
||||||
|
<footer>footer</footer>
|
||||||
|
```
|
|
@ -1,71 +0,0 @@
|
||||||
# External Navigation
|
|
||||||
|
|
||||||
In modern apps, and especially on the web, we often want to send our users to an
|
|
||||||
other website. [`External`] allows us to make a [`Link`] navigate to an
|
|
||||||
external page.
|
|
||||||
|
|
||||||
> You might already now about
|
|
||||||
> [external navigation failures](../failures/external.md). The [`Link`]
|
|
||||||
> component doesn't rely on the code path where those originate. Therefore a
|
|
||||||
> [`Link`] will never trigger an external navigation failure.
|
|
||||||
|
|
||||||
Strictly speaking, a [`Link`] is not necessary for navigating to external
|
|
||||||
targets, since by definition the router cannot handle them internally. However,
|
|
||||||
the [`Link`] component is more convenient to use, as it automatically sets the
|
|
||||||
`rel` attribute for the link, when the target is external.
|
|
||||||
|
|
||||||
## Code Example
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::prelude::*;
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::empty()
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
// links need to be inside a router, even if they navigate to an
|
|
||||||
// external page
|
|
||||||
Link {
|
|
||||||
target: NavigationTarget::External("https://dioxuslabs.com/".into()),
|
|
||||||
"Go to the dioxus home page"
|
|
||||||
}
|
|
||||||
Link {
|
|
||||||
target: "https://dioxuslabs.com/", // short form
|
|
||||||
"Go to the dioxus home page 2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# let html = dioxus_ssr::render(&vdom);
|
|
||||||
# assert_eq!(
|
|
||||||
# html,
|
|
||||||
# format!(
|
|
||||||
# "<a {attr1} {attr2}>{text}</a><a {attr1} {attr2}>{text} 2</a>",
|
|
||||||
# attr1 = r#"href="https://dioxuslabs.com/" dioxus-prevent-default="""#,
|
|
||||||
# attr2 = r#"class="" id="" rel="noopener noreferrer" target="""#,
|
|
||||||
# text = "Go to the dioxus home page"
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
```
|
|
||||||
|
|
||||||
> Note that the short form for an [`ExternalTarget`] looks like the short form
|
|
||||||
> for an [`InternalTarget`]. The router will create an [`ExternalTarget`] only
|
|
||||||
> if the URL is absolute.
|
|
||||||
|
|
||||||
[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External
|
|
||||||
[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal
|
|
||||||
[`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html
|
|
|
@ -1,59 +1,39 @@
|
||||||
# Links & Navigation
|
# Links & Navigation
|
||||||
|
|
||||||
When we split our app into pages, we need to provide our users with a way to
|
When we split our app into pages, we need to provide our users with a way to
|
||||||
navigate between them. On regular web pages we'd use an anchor element for that,
|
navigate between them. On regular web pages, we'd use an anchor element for that,
|
||||||
like this:
|
like this:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<a href="/other">Link to an other page</a>
|
<a href="/other">Link to an other page</a>
|
||||||
```
|
```
|
||||||
|
|
||||||
However, we cannot do that when using the router for two reasons:
|
However, we cannot do that when using the router for three reasons:
|
||||||
|
|
||||||
1. Anchor tags make the browser load a new page from the server. This takes a
|
1. Anchor tags make the browser load a new page from the server. This takes a
|
||||||
lot of time, and it is much faster to let the router handle the navigation
|
lot of time, and it is much faster to let the router handle the navigation
|
||||||
client-side.
|
client-side.
|
||||||
2. Navigation using anchor tags only works when the app is running inside a
|
2. Navigation using anchor tags only works when the app is running inside a
|
||||||
browser. This means we cannot use them inside apps using Dioxus Desktop.
|
browser. This means we cannot use them inside apps using Dioxus Desktop.
|
||||||
|
3. Anchor tags cannot check if the target page exists. This means we cannot
|
||||||
|
prevent accidentally linking to non-existent pages.
|
||||||
|
|
||||||
To solve these problems, the router provides us with a [`Link`] component we can
|
To solve these problems, the router provides us with a [`Link`] component we can
|
||||||
use like this:
|
use like this:
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
{{#include ../../../examples/links.rs:nav}}
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
fn SomeComponent(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
Link {
|
|
||||||
target: NavigationTarget::Internal(String::from("/some/path")),
|
|
||||||
"Link text"
|
|
||||||
}
|
|
||||||
Link {
|
|
||||||
target: "/some/path", // short form
|
|
||||||
"Other link text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `target` in the example above is similar to the `href` of a regular anchor
|
The `target` in the example above is similar to the `href` of a regular anchor
|
||||||
element. However, it tells the router more about what kind of navigation it
|
element. However, it tells the router more about what kind of navigation it
|
||||||
should perform:
|
should perform. It accepts something that can be converted into a
|
||||||
|
[`NavigationTarget`]:
|
||||||
|
|
||||||
- The example uses [`Internal`]. We give it an arbitrary path that will be
|
- The example uses a Internal route. This is the most common type of navigation.
|
||||||
merged with the current URL.
|
It tells the router to navigate to a page within our app by passing a variant of a [`Routable`] enum. This type of navigation can never fail if the link component is used inside a router component.
|
||||||
- [`Named`] allows us to navigate within our app using predefined names.
|
- [`External`] allows us to navigate to URLs outside of our app. This is useful
|
||||||
See the chapter about [named navigation](./name.md) for more details.
|
for links to external websites. NavigationTarget::External accepts an URL to navigate to. This type of navigation can fail if the URL is invalid.
|
||||||
- [`External`] allows us to navigate to URLs outside of our app. See the
|
|
||||||
chapter about [external navigation](./external.md) for more details.
|
|
||||||
|
|
||||||
> The [`Link`] accepts several props that modify its behavior. See the API docs
|
> The [`Link`] accepts several props that modify its behavior. See the API docs
|
||||||
> for more details.
|
> for more details.
|
||||||
|
|
||||||
[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External
|
|
||||||
[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal
|
|
||||||
[`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html
|
|
||||||
[`Named`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Named
|
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
# Named Navigation
|
|
||||||
|
|
||||||
When creating large applications, it can become difficult to keep track of all
|
|
||||||
routes and how to navigate to them. It also can be hard to find all links to
|
|
||||||
them, which makes it difficult to change paths.
|
|
||||||
|
|
||||||
To solve these problems, the router implements named navigation. When we define
|
|
||||||
our routes we can give them arbitrary, unique names (completely independent from
|
|
||||||
the path) and later ask the router to navigate to those names. The router will
|
|
||||||
automatically create the actual path to navigate to, even inserting required
|
|
||||||
parameters.
|
|
||||||
|
|
||||||
_Named_ navigation has a few advantages over _path-based_ navigation:
|
|
||||||
|
|
||||||
- Links can be created without knowing the actual path.
|
|
||||||
- It is much easier to find all links to a specific route.
|
|
||||||
- The router knows what links are invalid (and will panic in debug builds).
|
|
||||||
|
|
||||||
> When the router encounters an invalid link in a release build, it has to
|
|
||||||
> handle that problem. You can hook into that process, to display a custom error
|
|
||||||
> message. See the chapter about
|
|
||||||
> [named navigation failures](../failures/named.md).
|
|
||||||
|
|
||||||
> The router will automatically define the name [`RootIndex`] to refer to the
|
|
||||||
> root index route (`/`).
|
|
||||||
>
|
|
||||||
> It will also add other names (all of them are in the prelude module) in
|
|
||||||
> certain conditions. None of these names can be used for app defined routes.
|
|
||||||
|
|
||||||
## Code Example
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::prelude::*;
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
|
|
||||||
// we define a unit struct which will serve as our name
|
|
||||||
struct TargetName;
|
|
||||||
|
|
||||||
fn Source(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
Link {
|
|
||||||
// instead of InternalTarget we use NamedTarget (via the `named` fn)
|
|
||||||
// we can use the returned value to add parameters or a query
|
|
||||||
target: named::<TargetName>().query("query"),
|
|
||||||
"Go to target"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Target(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Target" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| {
|
|
||||||
Segment::content(comp(Source))
|
|
||||||
.fixed(
|
|
||||||
"target_path",
|
|
||||||
Route::content(comp(Target)).name::<TargetName>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# let html = dioxus_ssr::render(&vdom);
|
|
||||||
# assert_eq!(
|
|
||||||
# html,
|
|
||||||
# format!(
|
|
||||||
# "<a {attr1} {attr2}>Go to target</a>",
|
|
||||||
# attr1 = r#"href="/target_path?query" dioxus-prevent-default="onclick""#,
|
|
||||||
# attr2 = r#"class="" id="" rel="" target="""#
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
```
|
|
||||||
|
|
||||||
## Check if a name is present
|
|
||||||
|
|
||||||
You can check if a specific name is present for the current route. This works
|
|
||||||
similar to getting the value of a [parameter route](../routes/parameter.md) and
|
|
||||||
the same restrictions apply.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::prelude::*;
|
|
||||||
struct SomeName;
|
|
||||||
|
|
||||||
fn Content(cx: Scope) -> Element {
|
|
||||||
let route = use_route(cx).expect("needs to be in router");
|
|
||||||
|
|
||||||
if route.is_at(&named::<SomeName>(), false) {
|
|
||||||
// do something
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...
|
|
||||||
# todo!()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[`RootIndex`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.RootIndex.html
|
|
|
@ -3,68 +3,25 @@
|
||||||
Sometimes we want our application to navigate to another page without having the
|
Sometimes we want our application to navigate to another page without having the
|
||||||
user click on a link. This is called programmatic navigation.
|
user click on a link. This is called programmatic navigation.
|
||||||
|
|
||||||
## Acquiring a [`Navigator`]
|
## Using a Navigator
|
||||||
|
|
||||||
To use programmatic navigation, we first have to acquire a [`Navigator`]. For
|
We can get a navigator with the [`use_navigator`] hook. This hook returns a [`Navigator`].
|
||||||
that purpose we can use the [`use_navigate`] hook.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::prelude::*;
|
|
||||||
|
|
||||||
fn Content(cx: Scope) -> Element {
|
|
||||||
let nav = use_navigate(cx).expect("called inside a router");
|
|
||||||
|
|
||||||
// ...
|
|
||||||
# unimplemented!()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Triggering a Navigation
|
|
||||||
|
|
||||||
We can use the [`Navigator`] to trigger four different kinds of navigation:
|
We can use the [`Navigator`] to trigger four different kinds of navigation:
|
||||||
|
|
||||||
- `push` will navigate to the target. It works like a regular anchor tag.
|
- `push` will navigate to the target. It works like a regular anchor tag.
|
||||||
- `replace` works like `push`, except that it replaces the current history entry
|
- `replace` works like `push`, except that it replaces the current history entry
|
||||||
instead of adding a new one. This means the prior page cannot be restored with
|
instead of adding a new one. This means the prior page cannot be restored with the browser's back button.
|
||||||
the browsers back button.
|
- `Go back` works like the browser's back button.
|
||||||
- `Go back` works like the browsers back button.
|
- `Go forward` works like the browser's forward button.
|
||||||
- `Go forward` works like the browsers forward button (the opposite of the back
|
|
||||||
button).
|
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
{{#include ../../../examples/navigator.rs:nav}}
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
#
|
|
||||||
fn Content(cx: Scope) -> Element {
|
|
||||||
let nav = use_navigate(cx).expect("called inside a router");
|
|
||||||
|
|
||||||
// push
|
|
||||||
nav.push("/target");
|
|
||||||
|
|
||||||
// replace
|
|
||||||
nav.replace("/target");
|
|
||||||
|
|
||||||
// go back
|
|
||||||
nav.go_back();
|
|
||||||
|
|
||||||
// go forward
|
|
||||||
nav.go_forward();
|
|
||||||
|
|
||||||
// ...
|
|
||||||
# unimplemented!()
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You might have noticed that, like [`Link`], the [`Navigator`]s `push` and
|
You might have noticed that, like [`Link`], the [`Navigator`]s `push` and
|
||||||
`replace` functions take a [`NavigationTarget`]. This means we can use
|
`replace` functions take a [`NavigationTarget`]. This means we can use either
|
||||||
[`Internal`], [`Named`] and [`External`].
|
[`Internal`], or [`External`] targets.
|
||||||
|
|
||||||
## External Navigation Targets
|
## External Navigation Targets
|
||||||
|
|
||||||
|
@ -72,13 +29,4 @@ Unlike a [`Link`], the [`Navigator`] cannot rely on the browser (or webview) to
|
||||||
handle navigation to external targets via a generated anchor element.
|
handle navigation to external targets via a generated anchor element.
|
||||||
|
|
||||||
This means, that under certain conditions, navigation to external targets can
|
This means, that under certain conditions, navigation to external targets can
|
||||||
fail. See the chapter about
|
fail.
|
||||||
[external navigation failures](../failures/external.md) for more details.
|
|
||||||
|
|
||||||
[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External
|
|
||||||
[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal
|
|
||||||
[`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html
|
|
||||||
[`Named`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Named
|
|
||||||
[`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html
|
|
||||||
[`Navigator`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/hooks/struct.Navigator.html
|
|
||||||
[`use_navigate`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_navigate.html
|
|
||||||
|
|
|
@ -1,242 +0,0 @@
|
||||||
# Outlets
|
|
||||||
|
|
||||||
[`Outlet`]s tell the router where to render content. In the following example
|
|
||||||
the active routes content will be rendered within the [`Outlet`].
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
|
|
||||||
fn Index(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Index" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::content(comp(Index))
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
header { "header" }
|
|
||||||
Outlet { }
|
|
||||||
footer { "footer" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# let html = dioxus_ssr::render(&vdom);
|
|
||||||
# assert_eq!(
|
|
||||||
# html,
|
|
||||||
# "<header>header</header><h1>Index</h1><footer>footer</footer>"
|
|
||||||
# );
|
|
||||||
```
|
|
||||||
|
|
||||||
The example above will output the following HTML (line breaks added for
|
|
||||||
readability):
|
|
||||||
|
|
||||||
```html
|
|
||||||
<header>header</header>
|
|
||||||
<h1>Index</h1>
|
|
||||||
<footer>footer</footer>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Nested Outlets
|
|
||||||
|
|
||||||
When using nested routes, we need to provide equally nested [`Outlet`]s.
|
|
||||||
|
|
||||||
> Learn more about [nested routes](./routes/nested.md) in their own chapter.
|
|
||||||
|
|
||||||
## Named Outlets
|
|
||||||
|
|
||||||
When building complex apps, we often need to display multiple pieces of content
|
|
||||||
simultaneously. For example, we might have a sidebar that changes its content in
|
|
||||||
sync with the main part of the page.
|
|
||||||
|
|
||||||
When defining our routes, we can use `RouteContentMulti` instead of
|
|
||||||
`RouteContent::Component` (we've been using this through the `Into` trait) to
|
|
||||||
tell the router about our content.
|
|
||||||
|
|
||||||
We then can use a named [`Outlet`] in our output, to tell the router where to
|
|
||||||
put the side content.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
#
|
|
||||||
|
|
||||||
fn Main(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
main { "Main Content" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AsideName;
|
|
||||||
fn Aside(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
aside { "Side Content" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| {
|
|
||||||
Segment::content(
|
|
||||||
multi(Some(comp(Main)))
|
|
||||||
.add_named::<AsideName>(comp(Aside))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
Outlet {
|
|
||||||
name: Name::of::<AsideName>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# let html = dioxus_ssr::render(&vdom);
|
|
||||||
# assert_eq!(html, "<main>Main Content</main><aside>Side Content</aside>");
|
|
||||||
```
|
|
||||||
|
|
||||||
The example above will output the following HTML (line breaks added for
|
|
||||||
readability):
|
|
||||||
|
|
||||||
```html
|
|
||||||
<main>Main Content</main>
|
|
||||||
<aside>Side Content</aside>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Outlet depth override
|
|
||||||
|
|
||||||
When nesting [`Outlet`]s, they communicate with each other. This allows the
|
|
||||||
nested [`Outlet`] to render the content of the nested route.
|
|
||||||
|
|
||||||
We can override the detected value. Be careful when doing so, it is incredibly
|
|
||||||
easy to create an unterminated recursion. See below for an example of that.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
#
|
|
||||||
fn RootContent(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Root" }
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn NestedContent(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h2 { "Nested" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
# history: Box::new(MemoryHistory::with_initial_path("/root").unwrap()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| {
|
|
||||||
Segment::empty().fixed(
|
|
||||||
"root",
|
|
||||||
Route::content(comp(RootContent)).nested(
|
|
||||||
Segment::content(comp(NestedContent))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet {
|
|
||||||
depth: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# let html = dioxus_ssr::render(&vdom);
|
|
||||||
# assert_eq!(html, "<h2>Nested</h2>");
|
|
||||||
```
|
|
||||||
|
|
||||||
The example above will output the following HTML (line breaks added for
|
|
||||||
readability):
|
|
||||||
|
|
||||||
```html
|
|
||||||
<h2>Nested</h2>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Outlet recursion
|
|
||||||
|
|
||||||
This code will create a crash due to an unterminated recursion using
|
|
||||||
[`Outlet`]s.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
#
|
|
||||||
fn Content(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Heyho!" }
|
|
||||||
Outlet {
|
|
||||||
depth: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(cx, &Default::default, &|| Segment::content(comp(Content)));
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The [`Outlet`] in the `App` component has no parent [`Outlet`], so its depth
|
|
||||||
will be `0`. When rendering for the path `/`, it therefore will render the
|
|
||||||
`Content` component.
|
|
||||||
|
|
||||||
The `Content` component will render an `h1` and an [`Outlet`]. That [`OUtlet`]
|
|
||||||
would usually have a depth of `1`, since it is a descendant of the [`Outlet`] in
|
|
||||||
the `App` component. However, we override its depth to `0`, so it will render
|
|
||||||
the `Content` component.
|
|
||||||
|
|
||||||
That means the `Content` component will recurse until someone (e.g. the OS) puts
|
|
||||||
a stop to it.
|
|
||||||
|
|
||||||
[`Outlet`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html
|
|
|
@ -1,89 +0,0 @@
|
||||||
# Query
|
|
||||||
|
|
||||||
Some apps use the query part of the URL to encode information. The router allows
|
|
||||||
you to easily access the query, as well as set it when navigating.
|
|
||||||
|
|
||||||
## Accessing the query
|
|
||||||
|
|
||||||
The [`use_route`] hook allows us to access the current query in two ways. The
|
|
||||||
returned `struct` contains a `query` field, that contains the query (without the
|
|
||||||
leading `?`).
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::prelude::*;
|
|
||||||
|
|
||||||
fn SomeComponent(cx: Scope) -> Element {
|
|
||||||
let route = use_route(cx).expect("nested in Router");
|
|
||||||
|
|
||||||
let query = route.query.clone().unwrap();
|
|
||||||
|
|
||||||
// ...
|
|
||||||
# unimplemented!()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Setting the query
|
|
||||||
|
|
||||||
When navigating we can tell the router to change the query. However, the method
|
|
||||||
we use to do this is very different, depending on how we specify our target.
|
|
||||||
|
|
||||||
### [`Internal`] and [`External`]
|
|
||||||
|
|
||||||
When using [`Internal`] or [`External`] we have to append our query manually.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
#
|
|
||||||
fn SomeComponent(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
Link {
|
|
||||||
target: NavigationTarget::Internal("/some/path?query=yes".into()),
|
|
||||||
"Internal target"
|
|
||||||
}
|
|
||||||
Link {
|
|
||||||
target: NavigationTarget::External("https://dioxuslab.com?query=yes".into()),
|
|
||||||
"External target"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### [`Named`]
|
|
||||||
|
|
||||||
When using [named navigation](./navigation/name.md) we can pass the query via
|
|
||||||
a function.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
# struct Target;
|
|
||||||
#
|
|
||||||
fn SomeComponent(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
Link {
|
|
||||||
target: named::<Target>().query("query=yes"),
|
|
||||||
"Query String"
|
|
||||||
}
|
|
||||||
Link {
|
|
||||||
target: named::<Target>().query(vec![("query", "yes")]),
|
|
||||||
"Query Vec"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External
|
|
||||||
[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal
|
|
||||||
[`Named`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Named
|
|
||||||
[`use_route`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_route.html
|
|
13
docs/router/src/reference/redirects.md
Normal file
13
docs/router/src/reference/redirects.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Redirects
|
||||||
|
|
||||||
|
In some cases, we may want to redirect our users to another page whenever they
|
||||||
|
open a specific path. We can tell the router to do this with the `#[redirect]`
|
||||||
|
attribute.
|
||||||
|
|
||||||
|
The `#[redirect]` attribute accepts a route and a closure with all of the parameters defined in the route. The closure must return a [`NavigationTarget`].
|
||||||
|
|
||||||
|
In the following example, we will redirect everybody from `/myblog` and `/myblog/:id` to `/blog` and `/blog/:id` respectively
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../examples/full_example.rs:router}}
|
||||||
|
```
|
|
@ -1,166 +0,0 @@
|
||||||
# Catch All Routes
|
|
||||||
|
|
||||||
Many modern web apps store parameters within their current path. This allows
|
|
||||||
users to share URLs that link to a specific bit of content. We can create this
|
|
||||||
functionality with catch all routes.
|
|
||||||
|
|
||||||
> If you want to change what route is active based on the format of the
|
|
||||||
> parameter, see [Matching Routes](./matching.md).
|
|
||||||
|
|
||||||
> The parameter will be URL decoded.
|
|
||||||
|
|
||||||
## Creating a content component
|
|
||||||
|
|
||||||
We start by creating a component that uses the parameters value.
|
|
||||||
|
|
||||||
We can get the current state of the router using the [`use_route`] hook. From
|
|
||||||
that state we can extract the current value of our parameter by using a key we
|
|
||||||
will later also define on our route.
|
|
||||||
|
|
||||||
> It is **VERY IMPORTANT** to drop the object returned by the [`use_route`]
|
|
||||||
> hook once our component finished rendering. Otherwise the entire router will
|
|
||||||
> be frozen.
|
|
||||||
|
|
||||||
> The [`use_route`] hook can only be used in components nested within a
|
|
||||||
> component that called [`use_router`].
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::prelude::*;
|
|
||||||
|
|
||||||
struct Name;
|
|
||||||
|
|
||||||
fn Greeting(cx: Scope) -> Element {
|
|
||||||
let route = use_route(cx).expect("is nested within a Router component");
|
|
||||||
let name = route.parameter::<Name>()
|
|
||||||
.map(|name| name.clone())
|
|
||||||
.unwrap_or(String::from("world"));
|
|
||||||
|
|
||||||
render! {
|
|
||||||
p { "Hello, {name}!" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Defining the routes
|
|
||||||
|
|
||||||
Now we can define our route. Unlike a fixed [`Route`], a [`ParameterRoute`]
|
|
||||||
needs two arguments to be created.
|
|
||||||
|
|
||||||
> Also note that each [`Segment`] can have exactly one parameter or
|
|
||||||
> [fallback route](./fallback.md).
|
|
||||||
>
|
|
||||||
> For that reason, the example below would not work in practice, but showing
|
|
||||||
> both forms (explicit and short) is more important for this example.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
# fn Greeting(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
#
|
|
||||||
struct Name;
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| {
|
|
||||||
Segment::empty()
|
|
||||||
.catch_all(ParameterRoute::content::<Name>(comp(Greeting)))
|
|
||||||
.catch_all((comp(Greeting), Name { })) // same in short
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
# unimplemented!()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Interaction with other routes
|
|
||||||
|
|
||||||
Each individual [`Segment`] can only ever have one active route. This means that
|
|
||||||
when a [`Segment`] has more than just a catch all route, the router has to
|
|
||||||
decide which is active. It does that this way:
|
|
||||||
|
|
||||||
0. If the segment is not specified (i.e. `/`), then the index route will be
|
|
||||||
active.
|
|
||||||
1. If a [_fixed_](./index.md#fixed-routes) route matches the current path, it
|
|
||||||
will be active.
|
|
||||||
2. If a [_matching_ route](./matching.md) matches the current path, it will be
|
|
||||||
active. _Matching_ routes are checked in the order they are defined.
|
|
||||||
3. If neither a _fixed_ nor a _matching_ route is active, the _catch all_ route
|
|
||||||
or [_fallback_ route](./fallback.md) will be.
|
|
||||||
|
|
||||||
Step 0 means that if we want a parameter to be empty, that needs to be specified
|
|
||||||
by the path, i.e. `//`.
|
|
||||||
|
|
||||||
> Be careful with using catch all routes on the root [`Segment`]. Navigating to
|
|
||||||
> paths starting with `//` will **NOT** work. This is not a limitation of the
|
|
||||||
> router, but rather of how relative URLs work.
|
|
||||||
>
|
|
||||||
> If you absolutely need an empty parameter on the root [`Segment`], a URL like
|
|
||||||
> this _could_ work:
|
|
||||||
>
|
|
||||||
> - `https://your-site.example//` for web sites
|
|
||||||
> - `dioxus://index.html//` for desktop apps
|
|
||||||
|
|
||||||
## Full Code
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
#
|
|
||||||
struct Name;
|
|
||||||
|
|
||||||
fn Greeting(cx: Scope) -> Element {
|
|
||||||
let route = use_route(cx).expect("is nested within a Router component");
|
|
||||||
let name = route.parameter::<Name>()
|
|
||||||
.map(|name| name.clone())
|
|
||||||
.unwrap_or(String::from("world"));
|
|
||||||
|
|
||||||
render! {
|
|
||||||
p { "Hello, {name}!" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
let routes = use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
# history: Box::new(MemoryHistory::with_initial_path("/Dioxus").unwrap()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::empty().catch_all((comp(Greeting), Name { }))
|
|
||||||
);
|
|
||||||
// ...
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# assert_eq!(
|
|
||||||
# dioxus_ssr::render(&vdom),
|
|
||||||
# "<p>Hello, Dioxus!</p>"
|
|
||||||
# );
|
|
||||||
```
|
|
||||||
|
|
||||||
[`ParameterRoute`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.ParameterRoute.html
|
|
||||||
[`Route`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Route.html
|
|
||||||
[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
|
|
||||||
[`use_route`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_route.html
|
|
||||||
[`use_router`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html
|
|
|
@ -1,140 +0,0 @@
|
||||||
# Fallback Routes
|
|
||||||
|
|
||||||
Sometimes the router might be unable to find a route for the provided path. We
|
|
||||||
might want it to show a prepared error message to our users in that case.
|
|
||||||
Fallback routes allow us to do that.
|
|
||||||
|
|
||||||
> This is especially important for use cases where users can manually change the
|
|
||||||
> path, like web apps running in the browser.
|
|
||||||
|
|
||||||
## A single global fallback
|
|
||||||
|
|
||||||
To catch all cases of invalid paths within our app, we can simply add a fallback
|
|
||||||
route to our root [`Segment`].
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
|
|
||||||
fn Index(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Index" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Fallback(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Error 404 - Not Found" }
|
|
||||||
p { "The page you asked for doesn't exist." }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
# history: Box::new(MemoryHistory::with_initial_path("/invalid").unwrap()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| {
|
|
||||||
Segment::content(comp(Index)).fallback(comp(Fallback))
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# assert_eq!(
|
|
||||||
# dioxus_ssr::render(&vdom),
|
|
||||||
# "<h1>Error 404 - Not Found</h1><p>The page you asked for doesn't exist.</p>"
|
|
||||||
# );
|
|
||||||
```
|
|
||||||
|
|
||||||
## More specific fallback routes
|
|
||||||
|
|
||||||
In some cases we might want to show different fallback content depending on what
|
|
||||||
section of our app the user is in.
|
|
||||||
|
|
||||||
For example, our app might have several settings pages under `/settings`, such
|
|
||||||
as the password settings `/settings/password` or the privacy settings
|
|
||||||
`/settings/privacy`. When our user is in the settings section, we want to show
|
|
||||||
them _"settings not found"_ instead of _"page not found"_.
|
|
||||||
|
|
||||||
We can easily do that by setting a fallback route on our nested [`Segment`]. It
|
|
||||||
will then replace the global fallback whenever our [`Segment`] was active.
|
|
||||||
|
|
||||||
Note the `.clear_fallback(false)` part. If we didn't add this, the fallback
|
|
||||||
content would be rendered inside the `Settings` component.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
|
|
||||||
// This example doesn't show the index or settings components. It only shows how
|
|
||||||
// to set up several fallback routes.
|
|
||||||
# fn Index(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn Settings(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn GeneralSettings(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn PasswordSettings(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn PrivacySettings(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
|
|
||||||
fn GlobalFallback(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Error 404 - Page Not Found" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn SettingsFallback(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Error 404 - Settings Not Found" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
# history: Box::new(MemoryHistory::with_initial_path("/settings/invalid").unwrap()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| {
|
|
||||||
Segment::empty()
|
|
||||||
.fixed("settings", Route::content(comp(Settings)).nested(
|
|
||||||
Segment::content(comp(GeneralSettings))
|
|
||||||
.fixed("password", comp(PasswordSettings))
|
|
||||||
.fixed("privacy", comp(PrivacySettings))
|
|
||||||
.fallback(comp(SettingsFallback))
|
|
||||||
.clear_fallback(true)
|
|
||||||
))
|
|
||||||
.fallback(comp(GlobalFallback))
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# assert_eq!(
|
|
||||||
# dioxus_ssr::render(&vdom),
|
|
||||||
# "<h1>Error 404 - Settings Not Found</h1>"
|
|
||||||
# );
|
|
||||||
```
|
|
||||||
|
|
||||||
[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
|
|
|
@ -1,143 +1,65 @@
|
||||||
# Defining Routes
|
# Defining Routes
|
||||||
|
|
||||||
When creating a router we need to pass it a [`Segment`]. It tells the router
|
When creating a [`Routable`] enum, we can define routes for our application using the `route("path")` attribute.
|
||||||
about all the routes of our app.
|
|
||||||
|
|
||||||
## Example content
|
## Route Segments
|
||||||
|
|
||||||
To get a good understanding of how we define routes we first need to prepare
|
Each route is made up of segments. Most segments are separated by `/` characters in the path.
|
||||||
some example content, so we can see the routing in action.
|
|
||||||
|
There are four fundamental types of segments:
|
||||||
|
|
||||||
|
1. [Static segments](#static-segments) are fixed strings that must be present in the path.
|
||||||
|
2. [Dynamic segments](#dynamic-segments) are types that can be parsed from a segment.
|
||||||
|
3. [Catch-all segments](#catch-all-segments) are types that can be parsed from multiple segments.
|
||||||
|
4. [Query segments](#query-segments) are types that can be parsed from the query string.
|
||||||
|
|
||||||
|
Routes are matched:
|
||||||
|
|
||||||
|
- First, from most specific to least specific (Static then Dynamic then Catch All) (Query is always matched)
|
||||||
|
- Then, if multiple routes match the same path, the order in which they are defined in the enum is followed.
|
||||||
|
|
||||||
|
## Static segments
|
||||||
|
|
||||||
|
Fixed routes match a specific path. For example, the route `#[route("/about")]` will match the path `/about`.
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
{{#include ../../../examples/static_segments.rs:route}}
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::prelude::*;
|
|
||||||
|
|
||||||
fn Index(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Welcome to our test site!" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Other(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
p { "some other content" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Index routes
|
## Dynamic Segments
|
||||||
|
|
||||||
The easiest thing to do is to define an index route.
|
Dynamic segments are in the form of `:name` where `name` is
|
||||||
|
the name of the field in the route variant. If the segment is parsed
|
||||||
|
successfully then the route matches, otherwise the matching continues.
|
||||||
|
|
||||||
Index routes act very similar to `index.html` files in most web servers. They
|
The segment can be of any type that implements `FromStr`.
|
||||||
are active, when we don't specify a route.
|
|
||||||
|
|
||||||
> Note that we wrap our `Index` component with [`comp`]. This is because of
|
|
||||||
> rust type system requirements.
|
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
{{#include ../../../examples/dynamic_segments.rs:route}}
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
# fn Index(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
#
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::content(comp(Index))
|
|
||||||
);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
# unimplemented!()
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fixed routes
|
## Catch All Segments
|
||||||
|
|
||||||
It is almost as easy to define a fixed route.
|
Catch All segments are in the form of `:...name` where `name` is the name of the field in the route variant. If the segments are parsed successfully then the route matches, otherwise the matching continues.
|
||||||
|
|
||||||
Fixed routes work similar to how web servers treat files. They are active, when
|
The segment can be of any type that implements `FromSegments`. (Vec<String> implements this by default)
|
||||||
specified in the path. In the example, the path must be `/other`.
|
|
||||||
|
|
||||||
> The path will be URL decoded before checking if it matches our route.
|
Catch All segments must be the _last route segment_ in the path (query segments are not counted) and cannot be included in nests.
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
{{#include ../../../examples/catch_all_segments.rs:route}}
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
# fn Index(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn Other(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
#
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::content(comp(Index)).fixed("other", comp(Other))
|
|
||||||
// ^ note the absence of a / prefix
|
|
||||||
);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
# unimplemented!()
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Full Code
|
## Query Segments
|
||||||
|
|
||||||
|
Query segments are in the form of `?:name` where `name` is the name of the field in the route variant.
|
||||||
|
|
||||||
|
Unlike [Dynamic Segments](#dynamic-segments) and [Catch All Segments](#catch-all-segments), parsing a Query segment must not fail.
|
||||||
|
|
||||||
|
The segment can be of any type that implements `FromQuery`.
|
||||||
|
|
||||||
|
Query segments must be the _after all route segments_ and cannot be included in nests.
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
{{#include ../../../examples/query_segments.rs:route}}
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
|
|
||||||
fn Index(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Welcome to our test site!" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Other(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
p { "some other content" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
# history: Box::new(MemoryHistory::with_initial_path("/other").unwrap()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::content(comp(Index)).fixed("other", comp(Other))
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# assert_eq!(
|
|
||||||
# dioxus_ssr::render(&vdom),
|
|
||||||
# "<p>some other content</p>"
|
|
||||||
# );
|
|
||||||
```
|
```
|
||||||
|
|
||||||
[`comp`]: https://docs.rs/dioxus-router/latest/dioxus_router/prelude/fn.comp.html
|
|
||||||
[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
|
|
||||||
|
|
|
@ -1,139 +0,0 @@
|
||||||
# Matching Routes
|
|
||||||
|
|
||||||
> Make sure you understand how [catch all routes](./catch_all.md) work before
|
|
||||||
> reading this page.
|
|
||||||
|
|
||||||
When accepting parameters via the path, some complex applications might need to
|
|
||||||
decide what route should be active based on the format of that parameter.
|
|
||||||
_Matching_ routes make it easy to implement such behavior.
|
|
||||||
|
|
||||||
> The parameter will be URL decoded, both for checking if the route is active
|
|
||||||
> and when it is provided to the application.
|
|
||||||
|
|
||||||
> The example below is only for showing _matching route_ functionality. It is
|
|
||||||
> unfit for all other purposes.
|
|
||||||
|
|
||||||
## Code Example
|
|
||||||
|
|
||||||
> Notice that the parameter of a _matching route_ has the same type as a
|
|
||||||
> [_catch all route_](./catch_all.md).
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
# extern crate regex;
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
struct Name;
|
|
||||||
|
|
||||||
fn GreetingFemale(cx: Scope) -> Element {
|
|
||||||
let route = use_route(cx).unwrap();
|
|
||||||
let name = route.parameter::<Name>()
|
|
||||||
.map(|name| {
|
|
||||||
let mut name = name.to_string();
|
|
||||||
name.remove(0);
|
|
||||||
name
|
|
||||||
})
|
|
||||||
.unwrap_or(String::from("Anonymous"));
|
|
||||||
|
|
||||||
render! {
|
|
||||||
p { "Hello Mrs. {name}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn GreetingMale(cx: Scope) -> Element {
|
|
||||||
let route = use_route(cx).unwrap();
|
|
||||||
let name = route.parameter::<Name>()
|
|
||||||
.map(|name| {
|
|
||||||
let mut name = name.to_string();
|
|
||||||
name.remove(0);
|
|
||||||
name
|
|
||||||
})
|
|
||||||
.unwrap_or(String::from("Anonymous"));
|
|
||||||
|
|
||||||
render! {
|
|
||||||
p { "Hello Mr. {name}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn GreetingWithoutGender(cx: Scope) -> Element {
|
|
||||||
let route = use_route(cx).unwrap();
|
|
||||||
let name = route.parameter::<Name>()
|
|
||||||
.map(|name| name.to_string())
|
|
||||||
.unwrap_or(String::from("Anonymous"));
|
|
||||||
|
|
||||||
render! {
|
|
||||||
p { "Hello {name}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn GreetingKenobi(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
p { "Hello there." }
|
|
||||||
p { "General Kenobi." }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
# history: Box::new(MemoryHistory::with_initial_path("/fAnna").unwrap()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| {
|
|
||||||
Segment::empty()
|
|
||||||
.fixed("kenobi", comp(GreetingKenobi))
|
|
||||||
.matching(
|
|
||||||
Regex::new("^f").unwrap(),
|
|
||||||
ParameterRoute::content::<Name>(comp(GreetingFemale))
|
|
||||||
)
|
|
||||||
.matching(
|
|
||||||
Regex::new("^m").unwrap(),
|
|
||||||
(comp(GreetingMale), Name { })
|
|
||||||
)
|
|
||||||
.catch_all((comp(GreetingWithoutGender), Name { }))
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# let html = dioxus_ssr::render(&vdom);
|
|
||||||
# assert_eq!(html, "<p>Hello Mrs. Anna</p>");
|
|
||||||
```
|
|
||||||
|
|
||||||
## Matcher
|
|
||||||
|
|
||||||
In the example above, both _matching routes_ use a regular expression to specify
|
|
||||||
when they match. However, _matching routes_ are not limited to those. They
|
|
||||||
accept all types that implement the [`Matcher`] trait.
|
|
||||||
|
|
||||||
For example, you could (but probably shouldn't) implement a matcher, that
|
|
||||||
matches all values with an even number of characters:
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
#
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct EvenMatcher;
|
|
||||||
|
|
||||||
impl Matcher for EvenMatcher {
|
|
||||||
fn matches(&self, value: &str) -> bool {
|
|
||||||
value.len() % 2 == 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[`Matcher`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/trait.Matcher.html
|
|
|
@ -1,63 +0,0 @@
|
||||||
# Multiple Components & Redirects
|
|
||||||
|
|
||||||
## Multiple Components
|
|
||||||
|
|
||||||
When creating complex apps we sometimes want to have multiple pieces of content
|
|
||||||
side by side. The router allows us to do this. For more details see the section
|
|
||||||
about [named `Outlet`s](../outlets.md#named-outlets).
|
|
||||||
|
|
||||||
## Redirects
|
|
||||||
|
|
||||||
In some cases we may want to redirect our users to another page whenever they
|
|
||||||
open a specific path. We can tell the router to do this when defining our
|
|
||||||
routes.
|
|
||||||
|
|
||||||
> Redirects to external pages only work in certain conditions. For more details
|
|
||||||
> see the chapter about [external navigation failures](../failures/external.md).
|
|
||||||
|
|
||||||
In the following example we will redirect everybody from `/` and `/start` to
|
|
||||||
`/home`.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
|
|
||||||
fn Home(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Home Page" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
# history: Box::new(MemoryHistory::with_initial_path("/home").unwrap()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| {
|
|
||||||
Segment::content(comp(Home))
|
|
||||||
// notice that we use RouteContent::Redirect instead of
|
|
||||||
// RouteContent::Content (which we have been using indirectly)
|
|
||||||
.fixed(
|
|
||||||
"home",
|
|
||||||
RouteContent::Redirect(NavigationTarget::Internal("/".into()))
|
|
||||||
)
|
|
||||||
.fixed("start", "/") // short form
|
|
||||||
});
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# let html = dioxus_ssr::render(&vdom);
|
|
||||||
# assert_eq!(html, "<h1>Home Page</h1>");
|
|
||||||
```
|
|
|
@ -19,199 +19,21 @@ We might want to map this structure to these paths and components:
|
||||||
/settings/privacy -> Settings { PrivacySettings }
|
/settings/privacy -> Settings { PrivacySettings }
|
||||||
```
|
```
|
||||||
|
|
||||||
Nested routes allow us to do this.
|
Nested routes allow us to do this without repeating /settings in every route.
|
||||||
|
|
||||||
## Route Depth
|
## Nesting
|
||||||
|
|
||||||
With nesting routes, the router manages content on multiple levels. In our
|
To nest routes, we use the `#[nest("path")]` and `#[end_nest]` attributes.
|
||||||
example, when the path is `/settings`, there are two levels of content:
|
|
||||||
|
|
||||||
0. The `Settings` component
|
The path in nest must not:
|
||||||
1. The `GeneralSettings` component
|
|
||||||
|
|
||||||
Dioxus Router uses the [`Outlet`] component to actually render content, but each
|
1. Contain a [Catch All Segment](index.md#catch-all-segments)
|
||||||
[`Outlet`] can only render content from one level. This means that for the
|
2. Contain a [Query Segment](index.md#query-segments)
|
||||||
content of nested routes to actually be rendered, we also need nested
|
|
||||||
[`Outlet`]s.
|
|
||||||
|
|
||||||
## Defining the content components
|
If you define a dynamic segment in a nest, it will be available to all child routes and layouts.
|
||||||
|
|
||||||
We start by creating the components we want the router to render.
|
To finish a nest, we use the `#[end_nest]` attribute or the end of the enum.
|
||||||
|
|
||||||
Take a look at the `Settings` component. When it gets rendered by an [`Outlet`],
|
|
||||||
it will render a second [`Outlet`]. Thus the second [`Outlet`] is nested within
|
|
||||||
the first one, and will in turn render our nested content.
|
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
{{#include ../../../examples/nest.rs:route}}
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
#
|
|
||||||
fn Settings(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Settings" }
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn GeneralSettings(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h2 { "General Settings" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn PWSettings(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h2 { "Password Settings" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn PrivacySettings(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h2 { "Privacy Settings" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Defining the root [`Segment`]
|
|
||||||
|
|
||||||
Now we create the [`Segment`] that we will pass to the router.
|
|
||||||
|
|
||||||
Note that we wrap `comp(Settings)` within a [`Route`]. For this exact code that
|
|
||||||
is unnecessary, as this would be done automatically. However, in the next step
|
|
||||||
we'll use a method of [`Route`], so we might as well add this now.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
# fn Settings(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
#
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::empty().fixed("settings", Route::content(comp(Settings)))
|
|
||||||
);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
# unimplemented!()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Defining the nested [`Segment`]
|
|
||||||
|
|
||||||
In order to create nested routes we need to create a nested [`Segment`]. We then
|
|
||||||
pass it to the [`Route`] on the root segment.
|
|
||||||
|
|
||||||
> A [`Segment`] always refers to one exact segment of the path.
|
|
||||||
>
|
|
||||||
> https://router.example/`root_segment`/`first_nested_segment`/`second_nested_segment`/...
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
# fn Settings(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn GeneralSettings(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn PWSettings(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn PrivacySettings(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
#
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::empty().fixed(
|
|
||||||
"settings",
|
|
||||||
Route::content(comp(Settings)).nested(
|
|
||||||
Segment::content(comp(GeneralSettings))
|
|
||||||
.fixed("password", comp(PWSettings))
|
|
||||||
.fixed("privacy", comp(PrivacySettings))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
# unimplemented!()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Full Code
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
#
|
|
||||||
fn Settings(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Settings" }
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn GeneralSettings(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h2 { "General Settings" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn PWSettings(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h2 { "Password Settings" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn PrivacySettings(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h2 { "Privacy Settings" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
# history: Box::new(MemoryHistory::with_initial_path("/settings/privacy").unwrap()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::empty().fixed(
|
|
||||||
"settings",
|
|
||||||
Route::content(comp(Settings)).nested(
|
|
||||||
Segment::content(comp(GeneralSettings))
|
|
||||||
.fixed("password", comp(PWSettings))
|
|
||||||
.fixed("privacy", comp(PrivacySettings))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# assert_eq!(
|
|
||||||
# dioxus_ssr::render(&vdom),
|
|
||||||
# "<h1>Settings</h1><h2>Privacy Settings</h2>"
|
|
||||||
# );
|
|
||||||
```
|
|
||||||
|
|
||||||
[`Outlet`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html
|
|
||||||
[`Route`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Route.html
|
|
||||||
[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
# Routing Update Callback
|
# Routing Update Callback
|
||||||
|
|
||||||
In some cases we might want to run custom code when the current route changes.
|
In some cases, we might want to run custom code when the current route changes.
|
||||||
For this reason, the [`RouterConfiguration`] exposes an `on_update` field.
|
For this reason, the [`RouterConfig`] exposes an `on_update` field.
|
||||||
|
|
||||||
## How does the callback behave?
|
## How does the callback behave?
|
||||||
|
|
||||||
The `on_update` is called whenever the current routing information changes. It
|
The `on_update` is called whenever the current routing information changes. It
|
||||||
is called after the router updated its internal state, but before depended
|
is called after the router updated its internal state, but before dependent components and hooks are updated.
|
||||||
components and hooks are updated.
|
|
||||||
|
|
||||||
If the callback returns a [`NavigationTarget`], the router will replace the
|
If the callback returns a [`NavigationTarget`], the router will replace the
|
||||||
current location with the specified target. It will not call the
|
current location with the specified target. It will not call the
|
||||||
|
@ -16,54 +15,11 @@ current location with the specified target. It will not call the
|
||||||
If at any point the router encounters a
|
If at any point the router encounters a
|
||||||
[navigation failure](./failures/index.md), it will go to the appropriate state
|
[navigation failure](./failures/index.md), it will go to the appropriate state
|
||||||
without calling the `on_update`. It doesn't matter if the invalid target
|
without calling the `on_update`. It doesn't matter if the invalid target
|
||||||
initiated the navigation, was found as a redirect target or returned by the
|
initiated the navigation, was found as a redirect target, or was returned by the
|
||||||
`on_update` itself.
|
`on_update` itself.
|
||||||
|
|
||||||
## Code Example
|
## Code Example
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
{{#include ../../examples/routing_update.rs:router}}
|
||||||
# extern crate dioxus;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
#
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_router::prelude::*;
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
on_update: Some(Arc::new(|state| -> Option<NavigationTarget> {
|
|
||||||
if state.path == "/" {
|
|
||||||
return Some("/home".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| Segment::empty().fixed("home", comp(Content))
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Content(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
p { "Some content" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# assert_eq!(dioxus_ssr::render(&mut vdom), "<p>Some content</p>");
|
|
||||||
```
|
```
|
||||||
|
|
||||||
[`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html
|
|
||||||
[`RouterConfiguration`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/struct.RouterConfiguration.html
|
|
||||||
|
|
|
@ -1,225 +0,0 @@
|
||||||
# Sitemap Generation
|
|
||||||
|
|
||||||
If you need a list of all routes you have defined (e.g. for statically
|
|
||||||
generating all pages), Dioxus Router provides functions to extract that
|
|
||||||
information from a [`Segment`].
|
|
||||||
|
|
||||||
## Preparing an app
|
|
||||||
|
|
||||||
We will start by preparing an app with some routes like we normally would.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
|
|
||||||
fn Home(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Home" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Fixed(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Fixed" }
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Nested(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h2 { "Nested" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ParameterName;
|
|
||||||
fn Parameter(cx: Scope) -> Element {
|
|
||||||
let route = use_route(cx).unwrap();
|
|
||||||
let param = route.parameter::<ParameterName>().unwrap_or_default();
|
|
||||||
|
|
||||||
render! {
|
|
||||||
h1 { "Parameter: {param}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
# synchronous: true,
|
|
||||||
history: Box::new(MemoryHistory::with_initial_path("/fixed/nested").unwrap()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&|| {
|
|
||||||
Segment::content(comp(Home))
|
|
||||||
.fixed(
|
|
||||||
"fixed",
|
|
||||||
Route::content(comp(Fixed)).nested(
|
|
||||||
Segment::empty().fixed("nested", comp(Nested))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.catch_all((comp(Parameter), ParameterName { }))
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# let mut vdom = VirtualDom::new(App);
|
|
||||||
# vdom.rebuild();
|
|
||||||
# assert_eq!(dioxus_ssr::render(&mut vdom), "<h1>Fixed</h1><h2>Nested</h2>");
|
|
||||||
```
|
|
||||||
|
|
||||||
## Modifying the app to make using sitemaps easier
|
|
||||||
|
|
||||||
Preparing our app for sitemap generation is quite easy. We just need to extract
|
|
||||||
our segment definition into its own function.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
# fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn Fixed(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn Nested(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# struct ParameterName;
|
|
||||||
# fn Parameter(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
#
|
|
||||||
fn App(cx: Scope) -> Element {
|
|
||||||
use_router(
|
|
||||||
cx,
|
|
||||||
&|| RouterConfiguration {
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
&prepare_routes
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
|
||||||
Outlet { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_routes() -> Segment<Component> {
|
|
||||||
Segment::content(comp(Home))
|
|
||||||
.fixed(
|
|
||||||
"fixed",
|
|
||||||
Route::content(comp(Fixed)).nested(
|
|
||||||
Segment::empty().fixed("nested", comp(Nested))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.catch_all((comp(Parameter), ParameterName { }))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Sitemaps with parameter names
|
|
||||||
|
|
||||||
The first variant to generate sitemaps is very simple. It finds all routes
|
|
||||||
within the [`Segment`] and adds them to the returned `Vec`.
|
|
||||||
|
|
||||||
Matching and parameter routes are represented by their `key`, prefixed with `\`.
|
|
||||||
Besides that `\`, all paths are URL encoded.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
# fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn Fixed(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn Nested(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# struct ParameterName;
|
|
||||||
# fn Parameter(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn prepare_routes() -> Segment<Component> {
|
|
||||||
# Segment::content(comp(Home))
|
|
||||||
# .fixed(
|
|
||||||
# "fixed",
|
|
||||||
# Route::content(comp(Fixed)).nested(
|
|
||||||
# Segment::empty().fixed("nested", comp(Nested))
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
# .catch_all((comp(Parameter), ParameterName { }))
|
|
||||||
# }
|
|
||||||
|
|
||||||
let expected = vec![
|
|
||||||
"/",
|
|
||||||
"/fixed",
|
|
||||||
"/fixed/nested",
|
|
||||||
// Usually, here would be a fourth result representing the parameter route.
|
|
||||||
// However, due to mdbook the name for this file would constantly change,
|
|
||||||
// which is why we cannot show it. It would look something like this:
|
|
||||||
// "/\\your_crate::ParameterName",
|
|
||||||
];
|
|
||||||
let mut sitemap = prepare_routes().gen_sitemap();
|
|
||||||
sitemap.remove(3); // see above
|
|
||||||
assert_eq!(sitemap, expected);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Sitemaps with actual parameter values
|
|
||||||
|
|
||||||
The second variant to generate sitemaps is a bit more involved. When it
|
|
||||||
encounters a parameter route, it inserts all values with a matching `key` that
|
|
||||||
were provided to it.
|
|
||||||
|
|
||||||
Matching routes only add their path if the value matches their regex.
|
|
||||||
|
|
||||||
All paths are URL encoded.
|
|
||||||
|
|
||||||
```rust, no_run
|
|
||||||
# // Hidden lines (like this one) make the documentation tests work.
|
|
||||||
use std::collections::{BTreeMap, HashSet};
|
|
||||||
# extern crate dioxus;
|
|
||||||
# use dioxus::prelude::*;
|
|
||||||
# extern crate dioxus_router;
|
|
||||||
# use dioxus_router::prelude::*;
|
|
||||||
# extern crate dioxus_ssr;
|
|
||||||
# fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn Fixed(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn Nested(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# struct ParameterName;
|
|
||||||
# fn Parameter(cx: Scope) -> Element { unimplemented!() }
|
|
||||||
# fn prepare_routes() -> Segment<Component> {
|
|
||||||
# Segment::content(comp(Home))
|
|
||||||
# .fixed(
|
|
||||||
# "fixed",
|
|
||||||
# Route::content(comp(Fixed)).nested(
|
|
||||||
# Segment::empty().fixed("nested", comp(Nested))
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
# .catch_all((comp(Parameter), ParameterName { }))
|
|
||||||
# }
|
|
||||||
|
|
||||||
let parameters = {
|
|
||||||
let mut parameters = BTreeMap::new();
|
|
||||||
|
|
||||||
parameters.insert(
|
|
||||||
Name::of::<ParameterName>(),
|
|
||||||
vec![
|
|
||||||
String::from("some-parameter-value"),
|
|
||||||
String::from("other-parameter-value")
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
parameters
|
|
||||||
};
|
|
||||||
|
|
||||||
let expected: Vec<String> = vec![
|
|
||||||
"/",
|
|
||||||
"/fixed",
|
|
||||||
"/fixed/nested",
|
|
||||||
"/some-parameter-value",
|
|
||||||
"/other-parameter-value",
|
|
||||||
].into_iter().map(String::from).collect();
|
|
||||||
assert_eq!(expected, prepare_routes().gen_parameter_sitemap(¶meters));
|
|
||||||
```
|
|
||||||
|
|
||||||
[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
|
|
15
docs/router/src/reference/static-generation.md
Normal file
15
docs/router/src/reference/static-generation.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Static Generation
|
||||||
|
|
||||||
|
## Getting the Sitemap
|
||||||
|
|
||||||
|
The [`Routable`] trait includes an associated [`SITE_MAP`] constant that contains the map of all of the routes in the enum.
|
||||||
|
|
||||||
|
By default, the sitemap is a tree of (static or dynamic) RouteTypes, but it can be flattened into a list of individual routes with the `.flatten()` method.
|
||||||
|
|
||||||
|
## Generating a Sitemap
|
||||||
|
|
||||||
|
To statically render pages, we need to flatten the route tree and generate a file for each route that contains only static segments:
|
||||||
|
|
||||||
|
```rust, no_run
|
||||||
|
{{#include ../../../../packages/router/examples/static_generation.rs}}
|
||||||
|
```
|
115
packages/router/examples/static_generation.rs
Normal file
115
packages/router/examples/static_generation.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_router::prelude::*;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::{path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
render_static_pages();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_static_pages() {
|
||||||
|
for route in Route::SITE_MAP
|
||||||
|
.iter()
|
||||||
|
.flat_map(|seg| seg.flatten().into_iter())
|
||||||
|
{
|
||||||
|
// check if this is a static segment
|
||||||
|
let mut file_path = PathBuf::from("./");
|
||||||
|
let mut full_path = String::new();
|
||||||
|
let mut is_static = true;
|
||||||
|
for segment in &route {
|
||||||
|
match segment {
|
||||||
|
SegmentType::Static(s) => {
|
||||||
|
file_path.push(s);
|
||||||
|
full_path += "/";
|
||||||
|
full_path += s;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// skip routes with any dynamic segments
|
||||||
|
is_static = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_static {
|
||||||
|
let route = Route::from_str(&full_path).unwrap();
|
||||||
|
let mut vdom = VirtualDom::new_with_props(RenderPath, RenderPathProps { path: route });
|
||||||
|
let _ = vdom.rebuild();
|
||||||
|
|
||||||
|
file_path.push("index.html");
|
||||||
|
std::fs::create_dir_all(file_path.parent().unwrap()).unwrap();
|
||||||
|
let mut file = std::fs::File::create(file_path).unwrap();
|
||||||
|
|
||||||
|
let body = dioxus_ssr::render(&vdom);
|
||||||
|
let html = format!(
|
||||||
|
r#"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#,
|
||||||
|
full_path, body
|
||||||
|
);
|
||||||
|
file.write_all(html.as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn RenderPath(cx: Scope, path: Route) -> Element {
|
||||||
|
let path = path.clone();
|
||||||
|
render! {
|
||||||
|
Router {
|
||||||
|
config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Blog(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
div {
|
||||||
|
"Blog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Post(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
div {
|
||||||
|
"Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Home(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
div {
|
||||||
|
"Home"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Routable)]
|
||||||
|
enum Route {
|
||||||
|
#[nest("/blog")]
|
||||||
|
#[route("/")]
|
||||||
|
Blog {},
|
||||||
|
#[route("/post")]
|
||||||
|
Post {},
|
||||||
|
#[end_nest]
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ pub mod navigation;
|
||||||
pub mod routable;
|
pub mod routable;
|
||||||
|
|
||||||
/// Components interacting with the router.
|
/// Components interacting with the router.
|
||||||
mod components {
|
pub mod components {
|
||||||
mod default_errors;
|
mod default_errors;
|
||||||
pub use default_errors::*;
|
pub use default_errors::*;
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ mod router_cfg;
|
||||||
mod history;
|
mod history;
|
||||||
|
|
||||||
/// Hooks for interacting with the router in components.
|
/// Hooks for interacting with the router in components.
|
||||||
mod hooks {
|
pub mod hooks {
|
||||||
mod use_router;
|
mod use_router;
|
||||||
pub(crate) use use_router::*;
|
pub(crate) use use_router::*;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue