finish router refrence
This commit is contained in:
1 parent
07446386e6
commit
b5a2f0d6cb
61 files changed
+676
-1908
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user