mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-27 14:40:44 +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
|
||||
#[route("/")]
|
||||
// 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)]
|
||||
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)
|
||||
- [Redirection Perfection](./example/redirection-perfection.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}}
|
||||
```
|
||||
|
||||
## To Route or Not to Route
|
||||
## Creating Routes
|
||||
|
||||
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.
|
||||
|
@ -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
|
||||
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.
|
||||
|
||||
|
|
|
@ -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}}
|
||||
```
|
||||
|
||||
[`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
|
||||
[`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html
|
||||
[`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/navigation/enum.NavigationTarget.html#variant.Internal
|
||||
[`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
|
||||
into two sections:
|
||||
|
||||
1. The [Reference](./reference/index.md) part explains individual features in
|
||||
depth. You can read it start to finish, or you can read individual chapters
|
||||
1. The [reference](./reference/index.md) section explains individual features in
|
||||
depth. You can read it from start to finish, or you can read individual chapters
|
||||
in whatever order you want.
|
||||
2. If you prefer a learning-by-doing approach, you can check out the
|
||||
_[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.
|
||||
|
||||
> 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
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
- [`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
|
||||
> [`programmatic navigation`](./navigation/programmatic.md).
|
||||
|
||||
```rust, no_run, 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 HistoryNavigation(cx: Scope) -> Element {
|
||||
render! {
|
||||
GoBackButton {
|
||||
"Back to the Past"
|
||||
}
|
||||
GoForwardButton {
|
||||
"Back to the Future" /* You see what I did there? 😉 */
|
||||
}
|
||||
}
|
||||
}
|
||||
```rust, no_run
|
||||
{{#include ../../examples/history_buttons.rs:history_buttons}}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Importantly, neither [`WebHistory`] nor [`WebHashHistory`] support that feature.
|
||||
Importantly, neither [`WebHistory`] supports that feature.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
[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
|
||||
|
||||
In order to provide the ability to traverse the navigation history, the router
|
||||
uses [`HistoryProvider`]s. Those implement the actual back-and-forth
|
||||
functionality.
|
||||
[`HistoryProvider`]s are used by the router to keep track of the navigation history
|
||||
and update any external state (e.g. the browser's URL).
|
||||
|
||||
The router provides five [`HistoryProvider`]s, but you can also create your own.
|
||||
The five default implementations are:
|
||||
|
||||
- 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.
|
||||
|
||||
You can override the default history:
|
||||
|
||||
```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::*, history::WebHashHistory};
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
use_router(
|
||||
cx,
|
||||
&|| RouterConfiguration {
|
||||
history: Box::new(WebHashHistory::new(true)),
|
||||
..Default::default()
|
||||
},
|
||||
&|| Segment::empty()
|
||||
);
|
||||
|
||||
render! {
|
||||
Outlet { }
|
||||
}
|
||||
}
|
||||
{{#include ../../examples/history_provider.rs:app}}
|
||||
```
|
||||
|
||||
[`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
|
||||
|
||||
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
|
||||
described in the other chapters.
|
||||
|
||||
> Make sure you added the `dioxus-router` dependency as explained in the
|
||||
> [introduction](../index.md).
|
||||
|
||||
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
|
||||
add it by using the [`use_router`] hook
|
||||
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.
|
||||
|
||||
First, we define the router with the router macro:
|
||||
|
||||
```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;
|
||||
|
||||
// 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>"
|
||||
# );
|
||||
{{#include ../../examples/first_route.rs:router}}
|
||||
```
|
||||
|
||||
[`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
|
||||
|
||||
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:
|
||||
|
||||
```html
|
||||
<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
|
||||
lot of time, and it is much faster to let the router handle the navigation
|
||||
client-side.
|
||||
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.
|
||||
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
|
||||
use like this:
|
||||
|
||||
```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(String::from("/some/path")),
|
||||
"Link text"
|
||||
}
|
||||
Link {
|
||||
target: "/some/path", // short form
|
||||
"Other link text"
|
||||
}
|
||||
}
|
||||
}
|
||||
{{#include ../../../examples/links.rs:nav}}
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
merged with the current URL.
|
||||
- [`Named`] allows us to navigate within our app using predefined names.
|
||||
See the chapter about [named navigation](./name.md) for more details.
|
||||
- [`External`] allows us to navigate to URLs outside of our app. See the
|
||||
chapter about [external navigation](./external.md) for more details.
|
||||
- The example uses a Internal route. This is the most common type of navigation.
|
||||
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.
|
||||
- [`External`] allows us to navigate to URLs outside of our app. This is useful
|
||||
for links to external websites. NavigationTarget::External accepts an URL to navigate to. This type of navigation can fail if the URL is invalid.
|
||||
|
||||
> The [`Link`] accepts several props that modify its behavior. See the API docs
|
||||
> 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
|
||||
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
|
||||
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 get a navigator with the [`use_navigator`] hook. This hook returns a [`Navigator`].
|
||||
|
||||
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.
|
||||
- `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
|
||||
the browsers back button.
|
||||
- `Go back` works like the browsers back button.
|
||||
- `Go forward` works like the browsers forward button (the opposite of the back
|
||||
button).
|
||||
instead of adding a new one. This means the prior page cannot be restored with the browser's back button.
|
||||
- `Go back` works like the browser's back button.
|
||||
- `Go forward` works like the browser's forward button.
|
||||
|
||||
```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");
|
||||
|
||||
// push
|
||||
nav.push("/target");
|
||||
|
||||
// replace
|
||||
nav.replace("/target");
|
||||
|
||||
// go back
|
||||
nav.go_back();
|
||||
|
||||
// go forward
|
||||
nav.go_forward();
|
||||
|
||||
// ...
|
||||
# unimplemented!()
|
||||
}
|
||||
{{#include ../../../examples/navigator.rs:nav}}
|
||||
```
|
||||
|
||||
You might have noticed that, like [`Link`], the [`Navigator`]s `push` and
|
||||
`replace` functions take a [`NavigationTarget`]. This means we can use
|
||||
[`Internal`], [`Named`] and [`External`].
|
||||
`replace` functions take a [`NavigationTarget`]. This means we can use either
|
||||
[`Internal`], or [`External`] 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.
|
||||
|
||||
This means, that under certain conditions, navigation to external targets can
|
||||
fail. See the chapter about
|
||||
[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
|
||||
fail.
|
||||
|
|
|
@ -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
|
||||
|
||||
When creating a router we need to pass it a [`Segment`]. It tells the router
|
||||
about all the routes of our app.
|
||||
When creating a [`Routable`] enum, we can define routes for our application using the `route("path")` attribute.
|
||||
|
||||
## Example content
|
||||
## Route Segments
|
||||
|
||||
To get a good understanding of how we define routes we first need to prepare
|
||||
some example content, so we can see the routing in action.
|
||||
Each route is made up of segments. Most segments are separated by `/` characters in the path.
|
||||
|
||||
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
|
||||
# // 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 Index(cx: Scope) -> Element {
|
||||
render! {
|
||||
h1 { "Welcome to our test site!" }
|
||||
}
|
||||
}
|
||||
|
||||
fn Other(cx: Scope) -> Element {
|
||||
render! {
|
||||
p { "some other content" }
|
||||
}
|
||||
}
|
||||
{{#include ../../../examples/static_segments.rs:route}}
|
||||
```
|
||||
|
||||
## 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
|
||||
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.
|
||||
The segment can be of any type that implements `FromStr`.
|
||||
|
||||
```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 Index(cx: Scope) -> Element { unimplemented!() }
|
||||
#
|
||||
fn App(cx: Scope) -> Element {
|
||||
use_router(
|
||||
cx,
|
||||
&|| RouterConfiguration {
|
||||
..Default::default()
|
||||
},
|
||||
&|| Segment::content(comp(Index))
|
||||
);
|
||||
|
||||
// ...
|
||||
# unimplemented!()
|
||||
}
|
||||
{{#include ../../../examples/dynamic_segments.rs:route}}
|
||||
```
|
||||
|
||||
## 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
|
||||
specified in the path. In the example, the path must be `/other`.
|
||||
The segment can be of any type that implements `FromSegments`. (Vec<String> implements this by default)
|
||||
|
||||
> 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
|
||||
# // 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 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!()
|
||||
}
|
||||
{{#include ../../../examples/catch_all_segments.rs:route}}
|
||||
```
|
||||
|
||||
## 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
|
||||
# // 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 { "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>"
|
||||
# );
|
||||
{{#include ../../../examples/query_segments.rs:route}}
|
||||
```
|
||||
|
||||
[`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 }
|
||||
```
|
||||
|
||||
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
|
||||
example, when the path is `/settings`, there are two levels of content:
|
||||
To nest routes, we use the `#[nest("path")]` and `#[end_nest]` attributes.
|
||||
|
||||
0. The `Settings` component
|
||||
1. The `GeneralSettings` component
|
||||
The path in nest must not:
|
||||
|
||||
Dioxus Router uses the [`Outlet`] component to actually render content, but each
|
||||
[`Outlet`] can only render content from one level. This means that for the
|
||||
content of nested routes to actually be rendered, we also need nested
|
||||
[`Outlet`]s.
|
||||
1. Contain a [Catch All Segment](index.md#catch-all-segments)
|
||||
2. Contain a [Query Segment](index.md#query-segments)
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
To finish a nest, we use the `#[end_nest]` attribute or the end of the enum.
|
||||
|
||||
```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 {
|
||||
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" }
|
||||
}
|
||||
}
|
||||
{{#include ../../../examples/nest.rs:route}}
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
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.
|
||||
In some cases, we might want to run custom code when the current route changes.
|
||||
For this reason, the [`RouterConfig`] exposes an `on_update` field.
|
||||
|
||||
## How does the callback behave?
|
||||
|
||||
The `on_update` is called whenever the current routing information changes. It
|
||||
is called after the router updated its internal state, but before depended
|
||||
components and hooks are updated.
|
||||
is called after the router updated its internal state, but before dependent components and hooks are updated.
|
||||
|
||||
If the callback returns a [`NavigationTarget`], the router will replace 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
|
||||
[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
|
||||
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.
|
||||
|
||||
## Code Example
|
||||
|
||||
```rust, no_run
|
||||
# // Hidden lines (like this one) make the documentation tests work.
|
||||
# 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>");
|
||||
{{#include ../../examples/routing_update.rs:router}}
|
||||
```
|
||||
|
||||
[`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;
|
||||
|
||||
/// Components interacting with the router.
|
||||
mod components {
|
||||
pub mod components {
|
||||
mod default_errors;
|
||||
pub use default_errors::*;
|
||||
|
||||
|
@ -37,7 +37,7 @@ mod router_cfg;
|
|||
mod history;
|
||||
|
||||
/// Hooks for interacting with the router in components.
|
||||
mod hooks {
|
||||
pub mod hooks {
|
||||
mod use_router;
|
||||
pub(crate) use use_router::*;
|
||||
|
||||
|
|
Loading…
Reference in a new issue