mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 20:53:06 +00:00
add rewritten router book and fix features code
This commit is contained in:
parent
911e878d83
commit
e3610738ab
38 changed files with 2909 additions and 449 deletions
7
docs/router/README.md
Normal file
7
docs/router/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# The router book
|
||||
|
||||
## How to run the tests
|
||||
- Navigate your terminal to this directory
|
||||
- Run `cargo clean`
|
||||
- Run `cargo build --all --F regex -F serde -F web`
|
||||
- Run `mdbook test -L ../../target/debug/deps/`
|
|
@ -4,3 +4,6 @@ language = "en"
|
|||
multilingual = false
|
||||
src = "src"
|
||||
title = "Dioxus Router"
|
||||
|
||||
[rust]
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
# Dioxus Router: Introduction
|
||||
Whether or not you're building a website, desktop app, or mobile app, organizing your app's views into "pages" can be an effective method for organization and maintainability.
|
||||
|
||||
Dioxus comes with a router built-in. To start utilizing Dioxus Router, enable the ``router`` feature in your ``Cargo.toml`` file.
|
||||
```toml
|
||||
[dependencies]
|
||||
dioxus = { version = "x.x.x", features = [.., "router"] }
|
||||
```
|
||||
|
||||
In this book you'll find a short [guide](./guide/index.md) to get up to speed with Dioxus Router, as well as the router's [reference](./reference/index.md).
|
|
@ -1,11 +1,33 @@
|
|||
# Summary
|
||||
|
||||
- [Introduction](./README.md)
|
||||
[Introduction](./index.md)
|
||||
|
||||
- [Guide](./guide/index.md)
|
||||
- [Getting Started](./guide/getting-started.md)
|
||||
- [Creating Our First Route](./guide/first-route.md)
|
||||
- [Building a Nest](./guide/building-a-nest.md)
|
||||
- [Redirection Perfection](./guide/redirection-perfection.md)
|
||||
- [Reference](./reference/index.md)
|
||||
- [X]()
|
||||
# Features
|
||||
- [Adding the Router to Your Application](./features/index.md)
|
||||
- [Defining Routes](./features/routes/index.md)
|
||||
- [Nested Routes](./features/routes/nested.md)
|
||||
- [Catch All Routes](./features/routes/catch_all.md)
|
||||
- [Matching Routes](./features/routes/matching.md)
|
||||
- [Fallback Routes (404 page)](./features/routes/fallback.md)
|
||||
- [Multiple Components & Redirects](./features/routes/multiple-and-redirect.md)
|
||||
- [Outlets](./features/outlets.md)
|
||||
- [Links & Navigation](./features/navigation/index.md)
|
||||
- [Named Navigation](./features/navigation/name.md)
|
||||
- [External Navigation](./features/navigation/external.md)
|
||||
- [Programmatic Navigation](./features/navigation/programmatic.md)
|
||||
- [Query](./features/query.md)
|
||||
- [Navigation Failures](./features/failures/index.md)
|
||||
- [Named Navigation Failure](./features/failures/named.md)
|
||||
- [External Navigation Failure](./features/failures/external.md)
|
||||
- [History Providers](./features/history-providers.md)
|
||||
- [History Buttons](./features/history-buttons.md)
|
||||
- [Sitemap Generation](./features/sitemap-generation.md)
|
||||
- [Routing Update Callback](./features/routing-update-callback.md)
|
||||
|
||||
# Example Project
|
||||
- [Overview](./example/index.md)
|
||||
- [Creating Our First Route](./example/first-route.md)
|
||||
- [Building a Nest](./example/building-a-nest.md)
|
||||
- [Navigation Targets](./example/navigation-targets.md)
|
||||
- [Redirection Perfection](./example/redirection-perfection.md)
|
||||
- [Full Code](./example/full-code.md)
|
||||
|
|
263
docs/router/src/example/building-a-nest.md
Normal file
263
docs/router/src/example/building-a-nest.md
Normal file
|
@ -0,0 +1,263 @@
|
|||
# Building a Nest
|
||||
Not a bird's nest! A nest of routes!
|
||||
|
||||
In this chapter we will begin to build the blog portion of our site which will
|
||||
include links, nested URLs, and URL parameters. We will also explore the use
|
||||
case of rendering components directly in the [`Router`].
|
||||
|
||||
## Site Navigation
|
||||
Our site visitors won't know all the available pages and blogs on our site so we
|
||||
should provide a navigation bar for them.
|
||||
Let's create a new `NavBar` 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::prelude::*;
|
||||
#
|
||||
fn NavBar(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
nav {
|
||||
ul { }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Our navbar will be a list of links going between our pages. We could always use
|
||||
an HTML anchor element but that would cause our page to reload unnecessarily.
|
||||
Instead we want to use the [`Link`] component provided by Dioxus Router.
|
||||
|
||||
The [`Link`] is similar to a regular `a` tag. It takes a target (for now a path,
|
||||
more on other targets later) and an element. Let's add our links
|
||||
|
||||
```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 NavBar(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
nav {
|
||||
ul {
|
||||
// new stuff starts here
|
||||
li { Link { target: InternalTarget(String::from("/")), "Home" } }
|
||||
li { Link {
|
||||
target: "/blog", // short form
|
||||
"Blog"
|
||||
} }
|
||||
// new stuff ends here
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> Using this method, the [`Link`] component only works for links within our
|
||||
> application. To learn more about navigation targets see
|
||||
> [here](./navigation-targets.md).
|
||||
|
||||
And finally, we add the navbar component in our app 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::prelude::*;
|
||||
# fn Home(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn NavBar(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
|
||||
#
|
||||
fn App(cx: Scope) -> Element {
|
||||
let routes = use_segment(&cx, || {
|
||||
Segment::new()
|
||||
.index(Home as Component)
|
||||
.fallback(PageNotFound as Component)
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
routes: routes.clone(),
|
||||
NavBar { } // this is new
|
||||
Outlet { }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
Now you should see a list of links near the top of your page. Click on one and
|
||||
you should seamlessly travel between pages.
|
||||
|
||||
### Active Link Styling
|
||||
You might want to style links differently, when their page is currently open.
|
||||
To achieve this, we can tell the [`Link`] to give its internal `a` tag a class
|
||||
in that case.
|
||||
|
||||
```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 NavBar(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
nav {
|
||||
ul {
|
||||
li { Link {
|
||||
target: InternalTarget(String::from("/")),
|
||||
active_class: "active", // this is new
|
||||
"Home"
|
||||
} }
|
||||
li { Link {
|
||||
target: "/blog",
|
||||
active_class: "active", // this is new
|
||||
"Blog"
|
||||
} }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> This will not be reflected in the [full example code](./full-code.md).
|
||||
|
||||
## URL Parameters and Nested Routes
|
||||
Many websites such as GitHub put parameters in their URL. For example,
|
||||
`https://github.com/DioxusLabs` utilizes the text after the domain to
|
||||
dynamically search and display content about an organization.
|
||||
|
||||
We want to store our blogs in a database and load them as needed. This'll help
|
||||
prevent our app from being bloated therefor providing faster load times. We also
|
||||
want our users to be able to send people a link to a specific blog post.
|
||||
|
||||
We could utilize a search page that loads a blog when clicked but then our users
|
||||
won't be able to share our blogs easily. This is where URL parameters come in.
|
||||
|
||||
The path to our blog will look like `/blog/myBlogPage`, `myBlogPage` being the
|
||||
URL parameter.
|
||||
|
||||
First, lets create a component that wraps around all blog content. This allows
|
||||
us to add a heading that tells the user they are on the blog.
|
||||
```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 Blog(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h1 { "Blog" }
|
||||
Outlet {}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> Note the `Outlet { }` component. For the components of a nested route to be
|
||||
> rendered, we need an equally nested outlet. For more details, see the
|
||||
> [nested routes](../features/routes/nested.md) chapter of the features section.
|
||||
|
||||
Now we'll create another index component, that'll be displayed when no blog post
|
||||
is selected:
|
||||
```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 BlogList(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h2 { "Choose a post" }
|
||||
ul {
|
||||
li { Link {
|
||||
target: "/blog/1",
|
||||
"Read the first blog post"
|
||||
} }
|
||||
li { Link {
|
||||
target: "/blog/2",
|
||||
"Read the second blog post"
|
||||
} }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
We also need to create a component that displays an actual blog post. Within
|
||||
this component we can use the `use_route` hook to gain access to our URL
|
||||
parameters:
|
||||
```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 BlogPost(cx: Scope) -> Element {
|
||||
let route = use_route(&cx).unwrap();
|
||||
|
||||
let post_id = route.parameters.get("post_id");
|
||||
let post = post_id
|
||||
.map(|id| id.to_string())
|
||||
.unwrap_or(String::from("unknown"));
|
||||
|
||||
cx.render(rsx! {
|
||||
h2 { "Blog Post: {post}"}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Finally, let's tell our router about those components.
|
||||
```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 Blog(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn BlogList(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn BlogPost(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn Home(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn NavBar(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
|
||||
#
|
||||
fn App(cx: Scope) -> Element {
|
||||
let routes = use_segment(&cx, || {
|
||||
Segment::default()
|
||||
.index(Home as Component)
|
||||
// new stuff starts here
|
||||
.fixed(
|
||||
"blog",
|
||||
Route::new(Blog as Component).nested(
|
||||
Segment::default()
|
||||
.index(BlogList as Component)
|
||||
.catch_all(("post_id", BlogPost as Component))
|
||||
),
|
||||
)
|
||||
// new stuff ends here
|
||||
.fallback(PageNotFound as Component)
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
routes: routes.clone(),
|
||||
NavBar { }
|
||||
Outlet { }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
That's it! If you head to `/blog/1` you should see our sample post.
|
||||
|
||||
## Conclusion
|
||||
In this chapter we utilized Dioxus Router's Link, URL Parameter, and `use_route`
|
||||
functionality to build the blog portion of our application. In the next chapter,
|
||||
we will go over how navigation targets (like the one we passed to our links)
|
||||
work.
|
||||
|
||||
[`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html
|
||||
[`Router`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Router.html
|
91
docs/router/src/example/example.rs
Normal file
91
docs/router/src/example/example.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_router::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_web::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
let routes = use_segment(&cx, || {
|
||||
Segment::default()
|
||||
.index(Home as Component)
|
||||
.fixed(
|
||||
"blog",
|
||||
Route::new(Blog as Component).nested(
|
||||
Segment::default().index(BlogList as Component).catch_all(
|
||||
ParameterRoute::new("post_id", BlogPost as Component).name(BlogPost),
|
||||
),
|
||||
),
|
||||
)
|
||||
.fixed("myblog", "/blog")
|
||||
.fallback(PageNotFound as Component)
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
routes: routes.clone(),
|
||||
NavBar {}
|
||||
Outlet {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn NavBar(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
nav {
|
||||
ul {
|
||||
li { Link { target: (RootIndex, []), "Home" } }
|
||||
li { Link { target: "/blog", "Blog" } }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn Home(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h1 { "Welcome to the Dioxus Blog!" }
|
||||
})
|
||||
}
|
||||
|
||||
fn Blog(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h1 { "Blog" }
|
||||
Outlet {}
|
||||
})
|
||||
}
|
||||
|
||||
fn BlogList(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h2 { "Choose a post" }
|
||||
ul {
|
||||
li { Link {
|
||||
target: (BlogPost, [("post_id", String::from("1"))]),
|
||||
"Read the first blog post"
|
||||
} }
|
||||
li { Link {
|
||||
target: (BlogPost, [("post_id", String::from("2"))]),
|
||||
"Read the second blog post"
|
||||
} }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn BlogPost(cx: Scope) -> Element {
|
||||
let route = use_route(&cx).unwrap();
|
||||
|
||||
let post_id = route.parameters.get("post_id");
|
||||
let post = post_id
|
||||
.map(|id| id.to_string())
|
||||
.unwrap_or(String::from("unknown"));
|
||||
|
||||
cx.render(rsx! {
|
||||
h2 { "Blog Post: {post}"}
|
||||
})
|
||||
}
|
||||
|
||||
fn PageNotFound(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h1 { "Page not found" }
|
||||
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
||||
})
|
||||
}
|
143
docs/router/src/example/first-route.md
Normal file
143
docs/router/src/example/first-route.md
Normal file
|
@ -0,0 +1,143 @@
|
|||
# Creating Our First Route
|
||||
In this chapter, we will start utilizing Dioxus Router and add a homepage and a
|
||||
404 page to our project.
|
||||
|
||||
## Fundamentals
|
||||
Dioxus Router works based on a [`Router`] component, a route definition in
|
||||
regular rust and [`Outlet`] components. If you've ever used [Vue Router],
|
||||
you should feel at home with Dioxus Router.
|
||||
|
||||
First we need an actual page to route to! Let's add a homepage component:
|
||||
```rust,no_run
|
||||
# // Hidden lines (like this one) make the documentation tests work.
|
||||
# extern crate dioxus;
|
||||
# use dioxus::prelude::*;
|
||||
#
|
||||
fn Home(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h1 { "Welcome to the Dioxus Blog!" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## To Route or Not to Route
|
||||
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.
|
||||
|
||||
To start using Dioxus Router, we need to use the [`Router`] component. All hooks
|
||||
and other components the Router provides can only be used as a descendant of
|
||||
a [`Router`] component.
|
||||
|
||||
However, before we can add the [`Router`] we need to describe our routes in a
|
||||
type it can understand:
|
||||
```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 Home(cx: Scope) -> Element { unimplemented!() }
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
let routes = use_segment(&cx, || {
|
||||
// we want our home page component to render as an index
|
||||
Segment::default().index(Home as Component)
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
p { "Hello, Dioxus!"}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Now we can replace the `p { "Hello, Dioxus!" }` with our [`Router`]. We also
|
||||
need to tell it where to render the content of the active route. Therefore we
|
||||
nest an [`Outlet`] inside it.
|
||||
```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 Home(cx: Scope) -> Element { unimplemented!() }
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
let routes = use_segment(&cx, || {
|
||||
Segment::default().index(Home as Component)
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
// new stuff starts here
|
||||
Router {
|
||||
routes: routes.clone() // pass in the routes we prepared before
|
||||
Outlet { }
|
||||
}
|
||||
// new stuff ends here
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
If you head to your application's browser tab, you should now see the text
|
||||
`Welcome to Dioxus Blog!` when on the root URL (`http://localhost:8080/`). If
|
||||
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 `/`. The _index_ functionality we used basically emulates how
|
||||
web servers treat `index.html` files.
|
||||
|
||||
## What if a Route Doesn't Exist?
|
||||
In our example Dioxus Router doesn't render anything. Many sites also have a
|
||||
"404" page for when a URL path leads to nowhere. Dioxus Router can do this too!
|
||||
|
||||
First, we create a new `PageNotFound` component.
|
||||
```rust,no_run
|
||||
# // Hidden lines (like this one) make the documentation tests work.
|
||||
# extern crate dioxus;
|
||||
# use dioxus::prelude::*;
|
||||
#
|
||||
fn PageNotFound(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h1 { "Page not found" }
|
||||
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Now to tell Dioxus Router to render our new component when no route exists.
|
||||
```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 Home(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
|
||||
#
|
||||
fn App(cx: Scope) -> Element {
|
||||
let routes = use_segment(&cx, || {
|
||||
Segment::default()
|
||||
.index(Home as Component)
|
||||
.fallback(PageNotFound as Component) // this is new
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
routes: routes.clone(),
|
||||
Outlet { }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Now when you go to a route that doesn't exist, you should see the page not found
|
||||
text.
|
||||
|
||||
## Conclusion
|
||||
In this chapter we learned how to create a route and tell Dioxus Router what
|
||||
component to render when the URL path is `/`. We also created a 404 page to
|
||||
handle when a route doesn't exist. Next, we'll create the blog portion of our
|
||||
site. We will utilize nested routes and URL parameters.
|
||||
|
||||
[`Outlet`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html
|
||||
[`Router`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Router.html
|
||||
[Vue Router]: https://router.vuejs.org/
|
9
docs/router/src/example/full-code.md
Normal file
9
docs/router/src/example/full-code.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Full Code
|
||||
|
||||
```rust,no_run
|
||||
# // Hidden lines (like this one) make the documentation tests work.
|
||||
# extern crate dioxus;
|
||||
# extern crate dioxus_router;
|
||||
# extern crate dioxus_web;
|
||||
{{#include example.rs}}
|
||||
```
|
30
docs/router/src/example/index.md
Normal file
30
docs/router/src/example/index.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Overview
|
||||
|
||||
In this guide you'll learn to effectively use Dioxus Router whether you're
|
||||
building a small todo app or the next FAANG company. We will create a small
|
||||
website with a blog, homepage, and more!
|
||||
|
||||
> To follow along with the router example, you'll need a working Dioxus app.
|
||||
> Check out the [Dioxus book][db] to get started.
|
||||
|
||||
> Make sure to add Dioxus Router as a dependency, as explained in the
|
||||
> [introduction](../index.md).
|
||||
|
||||
## You'll learn how to
|
||||
- Create routes and render "pages".
|
||||
- Utilize nested routes, create a navigation bar, and render content for a
|
||||
set of routes.
|
||||
- Gather URL parameters to dynamically display content.
|
||||
- Redirect your visitors wherever you want.
|
||||
|
||||
> **Disclaimer**
|
||||
>
|
||||
> The example will only display the features of Dioxus Router. It will not
|
||||
> include any actual functionality. To keep things simple we will only be using
|
||||
> a single file, this is not the recommended way of doing things with a real
|
||||
> application.
|
||||
|
||||
You can find the complete application in the [full code](./full-code.md)
|
||||
chapter.
|
||||
|
||||
[db]: https://dioxuslabs.com/guide/
|
151
docs/router/src/example/navigation-targets.md
Normal file
151
docs/router/src/example/navigation-targets.md
Normal file
|
@ -0,0 +1,151 @@
|
|||
# Navigation Targets
|
||||
In the previous chapter we learned how to create links to pages within our app.
|
||||
We told them where to go using the `target` property. This property takes a
|
||||
[`NavigationTarget`].
|
||||
|
||||
## What is a navigation target?
|
||||
A [`NavigationTarget`] is similar to the `href` of an HTML anchor element.It
|
||||
tells the router where to navigate to. The Dioxus Router knows three kinds of
|
||||
navigation targets:
|
||||
- [`InternalTarget`]: we already saw that. It's basically an `href`, but cannot
|
||||
link to content outside our app.
|
||||
- [`ExternalTarget`]: This works exactly like an HTML anchors `href`. In fact,
|
||||
it is just passed through. Don't use this for in-app navigation as it'll
|
||||
trigger a page reload by the browser.
|
||||
- [`NamedTarget`]: this is the most interesting form of navigation target. We'll look
|
||||
at it in detail in this chapter.
|
||||
|
||||
## External navigation
|
||||
If we need a link to an external page we can do it 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 GoToDioxus(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Link {
|
||||
target: ExternalTarget(String::from("https://dioxuslabs.com")),
|
||||
"Explicit ExternalTarget target"
|
||||
}
|
||||
Link {
|
||||
target: "https://dioxuslabs.com", // short form
|
||||
"Implicit ExternalTarget target"
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> Note that we can use a `str`, just like with [`InternalTarget`]s. The router
|
||||
> will convert a `str` to an [`ExternalTarget`] if the URL is absolute.
|
||||
|
||||
## Named navigation
|
||||
When defining our routes, we can optionally give them unique static names. This
|
||||
is required for a feature we call named navigation.
|
||||
|
||||
Up to now, when creating links we told the router the exact path to go to. With
|
||||
named navigation we instead give it a name, and let it figure out the path.
|
||||
|
||||
This has several advantages:
|
||||
- We don't have to remember absolute paths or care about what the current path
|
||||
is
|
||||
- changing paths later on won't break internal links
|
||||
- paths can easily be localized without affecting app logic
|
||||
|
||||
Let's try that now! First, we give our blog post route a name. We can reuse our
|
||||
`BlogPost` component as a name.
|
||||
```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 Blog(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn BlogList(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn BlogPost(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn Home(cx: Scope) -> Element { unimplemented!() }
|
||||
#
|
||||
fn App(cx: Scope) -> Element {
|
||||
let routes = use_segment(&cx, || {
|
||||
Segment::default()
|
||||
.index(Home as Component)
|
||||
.fixed(
|
||||
"blog",
|
||||
Route::new(Blog as Component).nested(
|
||||
Segment::default().index(BlogList as Component).catch_all(
|
||||
// notice the name at the end of the line
|
||||
ParameterRoute::new("post_id", BlogPost as Component).name(BlogPost),
|
||||
),
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
// ...
|
||||
# unimplemented!()
|
||||
}
|
||||
```
|
||||
|
||||
Now we can change the targets of the links in our `BlogList` 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::prelude::*;
|
||||
# fn BlogPost(cx: Scope) -> Element { unimplemented!() }
|
||||
#
|
||||
fn BlogList(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h2 { "Choose a post" }
|
||||
ul {
|
||||
li { Link {
|
||||
target: (BlogPost, [("post_id", String::from("1"))]),
|
||||
"Read the first blog post"
|
||||
} }
|
||||
li { Link {
|
||||
target: (BlogPost, [("post_id", String::from("2"))], "query"),
|
||||
"Read the second blog post"
|
||||
} }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, a [`NamedTarget`] requires three fields:
|
||||
1. the name to navigate to
|
||||
2. a `Vec` containing all parameters that need to be inserted into the path
|
||||
3. optionally a query string to use.
|
||||
|
||||
|
||||
### The special root index name
|
||||
Whether we define any names or not, the router always knows about the
|
||||
[`RootIndex`] name. Navigating to it tells the router to go to `/`.
|
||||
|
||||
We can change the link in our `NavBar` component to take advantage 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::prelude::*;
|
||||
#
|
||||
fn NavBar(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
nav {
|
||||
ul {
|
||||
li { Link { target: (RootIndex, []), "Home" } }
|
||||
li { Link { target: "/blog", "Blog" } }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
[`ExternalTarget`]: https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.ExternalTarget
|
||||
[`InternalTarget`]: https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.InternalTarget
|
||||
[`NamedTarget`]: https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.NamedTarget
|
||||
[`NavigationTarget`]: https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html
|
||||
[`RootIndex`]: https://docs.rs/dioxus-router/latest/dioxus_router/names/struct.RootIndex.html
|
64
docs/router/src/example/redirection-perfection.md
Normal file
64
docs/router/src/example/redirection-perfection.md
Normal file
|
@ -0,0 +1,64 @@
|
|||
# Redirection Perfection
|
||||
You're well on your way to becoming a routing master!
|
||||
|
||||
In this chapter we will cover utilizing redirects so you can take Rickrolling to
|
||||
the next level.
|
||||
|
||||
## What Is This Redirect Thing?
|
||||
A redirect is very simple. When dioxus encounters a redirect while finding out
|
||||
what components to render, it will redirect the user to the target of the
|
||||
redirect.
|
||||
|
||||
As a simple example, let's say you want user to still land on your blog, even
|
||||
if they used the path `/myblog`.
|
||||
|
||||
All we need to do is update our route definition in our app 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::prelude::*;
|
||||
# fn Blog(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn BlogList(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn BlogPost(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn Home(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn NavBar(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn App(cx: Scope) -> Element {
|
||||
let routes = use_segment(&cx, || {
|
||||
Segment::new()
|
||||
.index(Home as Component)
|
||||
.fixed(
|
||||
"blog",
|
||||
Route::new(Blog as Component).nested(
|
||||
Segment::new().index(BlogList as Component).catch_all(
|
||||
ParameterRoute::new("post_id", BlogPost as Component).name(BlogPost)
|
||||
),
|
||||
),
|
||||
)
|
||||
.fixed("myblog", "/blog") // this is new
|
||||
.fallback(PageNotFound as Component)
|
||||
});
|
||||
# unimplemented!()
|
||||
# }
|
||||
```
|
||||
|
||||
That's it! Now your users will be redirected to the blog.
|
||||
|
||||
Notice that the `"/blog"` `str` is a [navigation target](./navigation-targets.md).
|
||||
We could also use external or named targets.
|
||||
|
||||
### Conclusion
|
||||
Well done! You've completed the Dioxus Router guide book. You've built a small
|
||||
application and learned about the many things you can do with Dioxus Router.
|
||||
To continue your journey, you can find a list of challenges down below, or you
|
||||
can check out the [API reference](https://docs.rs/dioxus-router/).
|
||||
|
||||
### Challenges
|
||||
- Organize your components into seperate files for better maintainability.
|
||||
- Give your app some style if you haven't already.
|
||||
- Build an about page so your visitors know who you are.
|
||||
- Add a user system that uses URL parameters.
|
||||
- Create a simple admin system to create, delete, and edit blogs.
|
||||
- If you want to go to the max, hook up your application to a rest API and database.
|
72
docs/router/src/features/failures/external.md
Normal file
72
docs/router/src/features/failures/external.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
# 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();
|
||||
|
||||
cx.render(rsx! {
|
||||
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()
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
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
|
4
docs/router/src/features/failures/index.md
Normal file
4
docs/router/src/features/failures/index.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# 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.
|
62
docs/router/src/features/failures/named.md
Normal file
62
docs/router/src/features/failures/named.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Named Navigation Failure
|
||||
|
||||
When using [named navigation](../navigation/name.md), the router runs into a
|
||||
problem under these circumstances:
|
||||
1. The name we try to navigate to is not contained within our routes.
|
||||
2. The route we navigate to requires a parameter that we don't provide when
|
||||
triggering the navigation.
|
||||
|
||||
> Users cannot directly interact with named navigation. If a named navigation
|
||||
> failure occurs, your app (or the router) has a bug.
|
||||
|
||||
The router reacts to this problem differently, depending on our apps build kind.
|
||||
|
||||
## Debug
|
||||
When running a debug build, the router will `panic` whenever it encounters an
|
||||
invalid navigation. This ensures that we notice these problems when we are
|
||||
testing our application.
|
||||
|
||||
## Release
|
||||
When running a release build, the router can't just `panic`, as that would be a
|
||||
horrible user experience. Instead, it changes shows some fallback content.
|
||||
|
||||
> You can detect if the router is in the named navigation failure handling state
|
||||
> by [checking](../navigation/name.md#check-if-a-name-is-present) if the
|
||||
> [`FailureNamedNavigation`] name is present.
|
||||
|
||||
The default fallback explains to the user that an error occurred and asks them
|
||||
to report the bug to the app developer.
|
||||
|
||||
You can override it by setting the `failure_named_navigation` value of the
|
||||
[`RouterConfiguration`].
|
||||
|
||||
```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 NamedNavigationFallback(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h1 { "Named navigation failure!" }
|
||||
})
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
use_router(
|
||||
&cx,
|
||||
&|| RouterConfiguration {
|
||||
failure_named_navigation: comp(NamedNavigationFallback),
|
||||
..Default::default()
|
||||
},
|
||||
&|| Segment::empty()
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
Outlet { }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
[`FailureNamedNavigation`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.FailureNamedNavigation.html
|
||||
[`RouterConfiguration`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/struct.RouterConfiguration.html
|
51
docs/router/src/features/history-buttons.md
Normal file
51
docs/router/src/features/history-buttons.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
# 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.
|
||||
|
||||
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
|
||||
forward buttons:
|
||||
|
||||
- [`GoBackButton`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoBackButton.html)
|
||||
- [`GoForwardButton`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoForwardButton.html)
|
||||
|
||||
> If you want to navigate through the history programmatically, take a look at
|
||||
> [`programmatic navigation`](./navigation/programmatic.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::prelude::*;
|
||||
|
||||
fn HistoryNavigation(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
GoBackButton {
|
||||
"Back to the Past"
|
||||
}
|
||||
GoForwardButton {
|
||||
"Back to the Future" /* You see what I did there? 😉 */
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
As you might know, browsers usually disable the back and forward buttons if
|
||||
there is no history to navigate to. The routers 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.
|
||||
This is due to limitations of the browser History API.
|
||||
|
||||
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
|
||||
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
|
44
docs/router/src/features/history-providers.md
Normal file
44
docs/router/src/features/history-providers.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
# 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.
|
||||
|
||||
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 [`WebHashHistory`] also integrates with the browser, but uses the fragment
|
||||
part of the URL.
|
||||
|
||||
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()
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
Outlet { }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
[`HistoryProvider`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/trait.HistoryProvider.html
|
||||
[`MemoryHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.MemoryHistory.html
|
||||
[`WebHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html
|
||||
[`WebHashHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHashHistory.html
|
54
docs/router/src/features/index.md
Normal file
54
docs/router/src/features/index.md
Normal file
|
@ -0,0 +1,54 @@
|
|||
# Adding the Router to Your Application
|
||||
|
||||
In this chapter we will learn how to add the router to our app. By it self, 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
|
||||
|
||||
```rust
|
||||
# // 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()
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
h1 { "Our sites title" }
|
||||
|
||||
// The Outlet tells the Router where to render active content.
|
||||
Outlet { }
|
||||
})
|
||||
}
|
||||
#
|
||||
# let mut vdom = VirtualDom::new(App);
|
||||
# let _ = vdom.rebuild();
|
||||
# assert_eq!(
|
||||
# dioxus_ssr::render(&vdom),
|
||||
# "<h1>Our sites title</h1>"
|
||||
# );
|
||||
```
|
||||
|
||||
[`use_router`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html
|
70
docs/router/src/features/navigation/external.md
Normal file
70
docs/router/src/features/navigation/external.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
# 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
|
||||
# // 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()
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
// 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
|
56
docs/router/src/features/navigation/index.md
Normal file
56
docs/router/src/features/navigation/index.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
# 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,
|
||||
like this:
|
||||
|
||||
```html
|
||||
<a href="/other">Link to an other page</a>
|
||||
```
|
||||
|
||||
However, we cannot do that when using the router for two 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.
|
||||
|
||||
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 {
|
||||
cx.render(rsx! {
|
||||
Link {
|
||||
target: NavigationTarget::Internal(String::from("/some/path")),
|
||||
"Link text"
|
||||
}
|
||||
Link {
|
||||
target: "/some/path", // short form
|
||||
"Other link text"
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The `target` in the example above is similar to the `href` of a regular anchor
|
||||
element. However, it tells the router more about what kind of navigation it
|
||||
should perform:
|
||||
- The example uses [`InternalTarget`]. We give it an arbitrary path that will be
|
||||
merged with the current URL.
|
||||
- [`NamedTarget`] allows us to navigate within our app using predefined names.
|
||||
See the chapter about [named navigation](./name.md) for more details.
|
||||
- [`ExternalTarget`] allows us to navigate to URLs outside of our app. See the
|
||||
chapter about [external navigation](./external.md) for more details.
|
||||
|
||||
> The [`Link`] accepts several props that modify its behavior. See the API docs
|
||||
> 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
|
117
docs/router/src/features/navigation/name.md
Normal file
117
docs/router/src/features/navigation/name.md
Normal file
|
@ -0,0 +1,117 @@
|
|||
# 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
|
||||
# // 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 {
|
||||
cx.render(rsx! {
|
||||
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 {
|
||||
cx.render(rsx! {
|
||||
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>()
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
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
|
80
docs/router/src/features/navigation/programmatic.md
Normal file
80
docs/router/src/features/navigation/programmatic.md
Normal file
|
@ -0,0 +1,80 @@
|
|||
# Programmatic Navigation
|
||||
|
||||
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`]
|
||||
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 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).
|
||||
|
||||
```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!()
|
||||
}
|
||||
```
|
||||
|
||||
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`].
|
||||
|
||||
## External Navigation Targets
|
||||
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
|
247
docs/router/src/features/outlets.md
Normal file
247
docs/router/src/features/outlets.md
Normal file
|
@ -0,0 +1,247 @@
|
|||
# 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
|
||||
# // 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 {
|
||||
cx.render(rsx! {
|
||||
h1 { "Index" }
|
||||
})
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
use_router(
|
||||
&cx,
|
||||
&|| RouterConfiguration {
|
||||
# synchronous: true,
|
||||
..Default::default()
|
||||
},
|
||||
&|| Segment::content(comp(Index))
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
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
|
||||
# // 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 {
|
||||
cx.render(rsx! {
|
||||
main { "Main Content" }
|
||||
})
|
||||
}
|
||||
|
||||
struct AsideName;
|
||||
fn Aside(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
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))
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
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
|
||||
# // 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 {
|
||||
cx.render(rsx! {
|
||||
h1 { "Root" }
|
||||
Outlet { }
|
||||
})
|
||||
}
|
||||
|
||||
fn NestedContent(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
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))
|
||||
)
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
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 {
|
||||
cx.render(rsx! {
|
||||
h1 { "Heyho!" }
|
||||
Outlet {
|
||||
depth: 0,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
use_router(&cx, &Default::default, &|| Segment::content(comp(Content)));
|
||||
|
||||
cx.render(rsx! {
|
||||
Outlet { }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The [`Outlet`] directly within the [`Router`] 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 its a descendant of the [`Outlet`] in
|
||||
the [`Router`]. 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
|
85
docs/router/src/features/query.md
Normal file
85
docs/router/src/features/query.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
# 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 {
|
||||
cx.render(rsx! {
|
||||
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 {
|
||||
cx.render(rsx! {
|
||||
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
|
161
docs/router/src/features/routes/catch_all.md
Normal file
161
docs/router/src/features/routes/catch_all.md
Normal file
|
@ -0,0 +1,161 @@
|
|||
# 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"));
|
||||
|
||||
cx.render(rsx! {
|
||||
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
|
||||
# // 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"));
|
||||
|
||||
cx.render(rsx! {
|
||||
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 { }))
|
||||
);
|
||||
// ...
|
||||
cx.render(rsx! {
|
||||
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
|
138
docs/router/src/features/routes/fallback.md
Normal file
138
docs/router/src/features/routes/fallback.md
Normal file
|
@ -0,0 +1,138 @@
|
|||
# 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
|
||||
# // 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 {
|
||||
cx.render(rsx! {
|
||||
h1 { "Index" }
|
||||
})
|
||||
}
|
||||
|
||||
fn Fallback(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
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))
|
||||
}
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
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
|
||||
# // 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 {
|
||||
cx.render(rsx! {
|
||||
h1 { "Error 404 - Page Not Found" }
|
||||
})
|
||||
}
|
||||
|
||||
fn SettingsFallback(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
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))
|
||||
}
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
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
|
139
docs/router/src/features/routes/index.md
Normal file
139
docs/router/src/features/routes/index.md
Normal file
|
@ -0,0 +1,139 @@
|
|||
# Defining Routes
|
||||
|
||||
When creating a router we need to pass it a [`Segment`]. It tells the router
|
||||
about all the routes of our app.
|
||||
|
||||
## Example content
|
||||
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.
|
||||
|
||||
```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 {
|
||||
cx.render(rsx! {
|
||||
h1 { "Welcome to our test site!" }
|
||||
})
|
||||
}
|
||||
|
||||
fn Other(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
p { "some other content" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Index routes
|
||||
The easiest thing to do is to define an index route.
|
||||
|
||||
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.
|
||||
|
||||
```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!()
|
||||
}
|
||||
```
|
||||
|
||||
## Fixed routes
|
||||
It is almost as easy to define a fixed route.
|
||||
|
||||
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 path will be URL decoded before checking if it matches our route.
|
||||
|
||||
```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!()
|
||||
}
|
||||
```
|
||||
|
||||
## Full Code
|
||||
```rust
|
||||
# // 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 {
|
||||
cx.render(rsx! {
|
||||
h1 { "Welcome to our test site!" }
|
||||
})
|
||||
}
|
||||
|
||||
fn Other(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
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))
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
Outlet { }
|
||||
})
|
||||
}
|
||||
#
|
||||
# let mut vdom = VirtualDom::new(App);
|
||||
# vdom.rebuild();
|
||||
# assert_eq!(
|
||||
# dioxus_ssr::render(&vdom),
|
||||
# "<p>some other content</p>"
|
||||
# );
|
||||
```
|
||||
|
||||
[`comp`]: https://docs.rs/dioxus-router/latest/dioxus_router/prelude/fn.comp.html
|
||||
[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
|
137
docs/router/src/features/routes/matching.md
Normal file
137
docs/router/src/features/routes/matching.md
Normal file
|
@ -0,0 +1,137 @@
|
|||
# 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
|
||||
# // 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"));
|
||||
|
||||
cx.render(rsx! {
|
||||
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"));
|
||||
|
||||
cx.render(rsx! {
|
||||
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"));
|
||||
|
||||
cx.render(rsx! {
|
||||
p { "Hello {name}" }
|
||||
})
|
||||
}
|
||||
|
||||
fn GreetingKenobi(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
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 { }))
|
||||
}
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
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
|
61
docs/router/src/features/routes/multiple-and-redirect.md
Normal file
61
docs/router/src/features/routes/multiple-and-redirect.md
Normal file
|
@ -0,0 +1,61 @@
|
|||
# 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
|
||||
# // 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 {
|
||||
cx.render(rsx! {
|
||||
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
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
Outlet { }
|
||||
})
|
||||
}
|
||||
#
|
||||
# let mut vdom = VirtualDom::new(App);
|
||||
# vdom.rebuild();
|
||||
# let html = dioxus_ssr::render(&vdom);
|
||||
# assert_eq!(html, "<h1>Home Page</h1>");
|
||||
```
|
212
docs/router/src/features/routes/nested.md
Normal file
212
docs/router/src/features/routes/nested.md
Normal file
|
@ -0,0 +1,212 @@
|
|||
# Nested Routes
|
||||
|
||||
When developing bigger applications we often want to nest routes within each
|
||||
other. As an example, we might want to organize a settings menu using this
|
||||
pattern:
|
||||
|
||||
```plain
|
||||
└ Settings
|
||||
├ General Settings (displayed when opening the settings)
|
||||
├ Change Password
|
||||
└ Privacy Settings
|
||||
```
|
||||
|
||||
We might want to map this structure to these paths and components:
|
||||
|
||||
```plain
|
||||
/settings -> Settings { GeneralSettings }
|
||||
/settings/password -> Settings { PWSettings }
|
||||
/settings/privacy -> Settings { PrivacySettings }
|
||||
```
|
||||
|
||||
Nested routes allow us to do this.
|
||||
|
||||
## Route Depth
|
||||
With nesting routes, the router manages content on multiple levels. In our
|
||||
example, when the path is `/settings`, there are two levels of content:
|
||||
|
||||
0. The `Settings` component
|
||||
1. The `GeneralSettings` component
|
||||
|
||||
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.
|
||||
|
||||
## Defining the content components
|
||||
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.
|
||||
|
||||
```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 {
|
||||
cx.render(rsx! {
|
||||
h1 { "Settings" }
|
||||
Outlet { }
|
||||
})
|
||||
}
|
||||
|
||||
fn GeneralSettings(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h2 { "General Settings" }
|
||||
})
|
||||
}
|
||||
|
||||
fn PWSettings(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h2 { "Password Settings" }
|
||||
})
|
||||
}
|
||||
|
||||
fn PrivacySettings(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h2 { "Privacy Settings" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Defining the root [`Segment`]
|
||||
Now we create the [`Segment`] that we will pass to the router.
|
||||
|
||||
Note that we wrap `comp(Settings)` within a [`Route`]. For this exact code that
|
||||
is unnecessary, as this would be done automatically. However, in the next step
|
||||
we'll use a method of [`Route`], so we might as well add this now.
|
||||
|
||||
```rust,no_run
|
||||
# // Hidden lines (like this one) make the documentation tests work.
|
||||
# extern crate dioxus;
|
||||
# use dioxus::prelude::*;
|
||||
# extern crate dioxus_router;
|
||||
# use dioxus_router::prelude::*;
|
||||
# fn Settings(cx: Scope) -> Element { unimplemented!() }
|
||||
#
|
||||
fn App(cx: Scope) -> Element {
|
||||
use_router(
|
||||
&cx,
|
||||
&|| RouterConfiguration {
|
||||
..Default::default()
|
||||
},
|
||||
&|| Segment::empty().fixed("settings", Route::content(comp(Settings)))
|
||||
);
|
||||
|
||||
// ...
|
||||
# unimplemented!()
|
||||
}
|
||||
```
|
||||
|
||||
## Defining the nested [`Segment`]
|
||||
In order to create nested routes we need to create a nested [`Segment`]. We then
|
||||
pass it to the [`Route`] on the root segment.
|
||||
|
||||
> A [`Segment`] always refers to one exact segment of the path.
|
||||
>
|
||||
> https://router.example/`root_segment`/`first_nested_segment`/`second_nested_segment`/...
|
||||
|
||||
```rust,no_run
|
||||
# // Hidden lines (like this one) make the documentation tests work.
|
||||
# extern crate dioxus;
|
||||
# use dioxus::prelude::*;
|
||||
# extern crate dioxus_router;
|
||||
# use dioxus_router::prelude::*;
|
||||
# fn Settings(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn GeneralSettings(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn PWSettings(cx: Scope) -> Element { unimplemented!() }
|
||||
# fn PrivacySettings(cx: Scope) -> Element { unimplemented!() }
|
||||
#
|
||||
fn App(cx: Scope) -> Element {
|
||||
use_router(
|
||||
&cx,
|
||||
&|| RouterConfiguration {
|
||||
..Default::default()
|
||||
},
|
||||
&|| Segment::empty().fixed(
|
||||
"settings",
|
||||
Route::content(comp(Settings)).nested(
|
||||
Segment::content(comp(GeneralSettings))
|
||||
.fixed("password", comp(PWSettings))
|
||||
.fixed("privacy", comp(PrivacySettings))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// ...
|
||||
# unimplemented!()
|
||||
}
|
||||
```
|
||||
|
||||
## Full Code
|
||||
```rust
|
||||
# // 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 {
|
||||
cx.render(rsx! {
|
||||
h1 { "Settings" }
|
||||
Outlet { }
|
||||
})
|
||||
}
|
||||
|
||||
fn GeneralSettings(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h2 { "General Settings" }
|
||||
})
|
||||
}
|
||||
|
||||
fn PWSettings(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h2 { "Password Settings" }
|
||||
})
|
||||
}
|
||||
|
||||
fn PrivacySettings(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
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))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
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
|
67
docs/router/src/features/routing-update-callback.md
Normal file
67
docs/router/src/features/routing-update-callback.md
Normal file
|
@ -0,0 +1,67 @@
|
|||
# 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.
|
||||
|
||||
## 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.
|
||||
|
||||
If the callback returns a [`NavigationTarget`], the router will replace the
|
||||
current location with the specified target. It will not call the
|
||||
`on_update` again.
|
||||
|
||||
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
|
||||
`on_update` itself.
|
||||
|
||||
## Code Example
|
||||
```rust
|
||||
# // 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>");
|
||||
```
|
||||
|
||||
[`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
|
220
docs/router/src/features/sitemap-generation.md
Normal file
220
docs/router/src/features/sitemap-generation.md
Normal file
|
@ -0,0 +1,220 @@
|
|||
# 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
|
||||
# // 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 {
|
||||
cx.render(rsx! {
|
||||
h1 { "Home" }
|
||||
})
|
||||
}
|
||||
|
||||
fn Fixed(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h1 { "Fixed" }
|
||||
Outlet { }
|
||||
})
|
||||
}
|
||||
|
||||
fn Nested(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h2 { "Nested" }
|
||||
})
|
||||
}
|
||||
|
||||
struct ParameterName;
|
||||
fn Parameter(cx: Scope) -> Element {
|
||||
let route = use_route(&cx).unwrap();
|
||||
let param = route.parameter::<ParameterName>().unwrap_or_default();
|
||||
|
||||
cx.render(rsx! {
|
||||
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 { }))
|
||||
}
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
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
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
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
|
||||
# // 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
|
||||
# // 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
|
|
@ -1,201 +0,0 @@
|
|||
# Building a Nest
|
||||
Not a bird's nest! A nest of routes!
|
||||
|
||||
In this chapter we will begin to build the blog portion of our site which will include links, nested URLs, and URL parameters. We will also explore the use case of rendering components outside of routes.
|
||||
|
||||
### Site Navigation
|
||||
Our site visitors won't know all the available pages and blogs on our site so we should provide a navigation bar for them.
|
||||
Let's create a new ``navbar`` component:
|
||||
```rs
|
||||
fn navbar(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
ul {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
Our navbar will be a list of links going between our pages. We could always use an HTML anchor element but that would cause our page to unnecessarily reload. Instead we want to use the ``Link`` component provided by Dioxus Router.
|
||||
|
||||
The Link component is very similar to the Route component. It takes a path and an element. Add the Link component into your use statement and then add some links:
|
||||
```rs
|
||||
use dioxus::{
|
||||
prelude::*,
|
||||
router::{Route, Router, Link}, // UPDATED
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
fn navbar(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
ul {
|
||||
// NEW
|
||||
Link { to: "/", "Home"}
|
||||
br {}
|
||||
Link { to: "/blog", "Blog"}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
>By default, the Link component only works for links within your application. To link to external sites, add the ``external: true`` property.
|
||||
>```rs
|
||||
>Link { to: "https://github.com", external: true, "GitHub"}
|
||||
>```
|
||||
|
||||
And finally, use the navbar component in your app component:
|
||||
```rs
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
p { "-- Dioxus Blog --" }
|
||||
self::navbar {} // NEW
|
||||
Route { to: "/", self::homepage {}}
|
||||
Route { to: "", self::page_not_found {}}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
Now you should see a list of links near the top of your page. Click on one and you should seamlessly travel between pages.
|
||||
|
||||
##### WIP: Active Link Styling
|
||||
|
||||
### URL Parameters and Nested Routes
|
||||
Many websites such as GitHub put parameters in their URL. For example, ``github.com/DioxusLabs`` utilizes the text after the domain to dynamically search and display content about an organization.
|
||||
|
||||
We want to store our blogs in a database and load them as needed. This'll help prevent our app from being bloated therefor providing faster load times. We also want our users to be able to send people a link to a specific blog post.
|
||||
We could utilize a search page that loads a blog when clicked but then our users won't be able to share our blogs easily. This is where URL parameters come in. And finally, we also want our site to tell users they are on a blog page whenever the URL starts with``/blog``.
|
||||
|
||||
The path to our blog will look like ``/blog/myBlogPage``. ``myBlogPage`` being the URL parameter.
|
||||
Dioxus Router uses the ``:name`` pattern so our route will look like ``/blog/:post``.
|
||||
|
||||
First, lets tell users when they are on a blog page. Add a new route in your app component.
|
||||
```rs
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
p { "-- Dioxus Blog --" }
|
||||
self::navbar {}
|
||||
Route { to: "/", self::homepage {}}
|
||||
// NEW
|
||||
Route {
|
||||
to: "/blog",
|
||||
}
|
||||
Route { to: "", self::page_not_found {}}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
Routes can take components as parameters and we know that a route is a component. We nest routes by doing exactly what they are called, nesting them:
|
||||
```rs
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
p { "-- Dioxus Blog --" }
|
||||
self::navbar {}
|
||||
Route { to: "/", self::homepage {}}
|
||||
Route {
|
||||
to: "/blog",
|
||||
Route { to: "/:post", "This is my blog post!" } // NEW
|
||||
}
|
||||
Route { to: "", self::page_not_found {}}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
Nesting our route like this isn't too helpful at first, but remember we want to tell users they are on a blog page. Let's move our ``p { "-- Dioxus Blog --" }`` inside of our ``/blog`` route.
|
||||
```rs
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
self::navbar {}
|
||||
Route { to: "/", self::homepage {}}
|
||||
Route {
|
||||
to: "/blog",
|
||||
p { "-- Dioxus Blog --" } // MOVED
|
||||
Route { to: "/:post", "This is my blog post!" }
|
||||
}
|
||||
Route { to: "", self::page_not_found {}}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
Now our ``-- Dioxus Blog --`` text will be displayed whenever a user is on a path that starts with ``/blog``. Displaying content in a way that is page-agnostic is useful when building navigation menus, footers, and similar.
|
||||
|
||||
All that's left is to handle our URL parameter. We will begin by creating a ``get_blog_post`` function. In a real site, this function would call an API endpoint to get a blog post from the database. However, that is out of the scope of this guide so we will be utilizing static text.
|
||||
```rs
|
||||
fn get_blog_post(id: &str) -> String {
|
||||
match id {
|
||||
"foo" => "Welcome to the foo blog post!".to_string(),
|
||||
"bar" => "This is the bar blog post!".to_string(),
|
||||
id => format!("Blog post '{id}' does not exist!")
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
Now that we have established our helper function, lets create a new ``blog_post`` component.
|
||||
```rs
|
||||
fn blog_post(cx: Scope) -> Element {
|
||||
let blog_text = "";
|
||||
|
||||
cx.render(rsx! {
|
||||
p { "{blog_text}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
All that's left is to extract the blog id from the URL and to call our helper function to get the blog text. To do this we need to utilize Dioxus Router's ``use_route`` hook.
|
||||
First start by adding ``use_route`` to your imports and then utilize the hook in your ``blog_post`` component.
|
||||
```rs
|
||||
use dioxus::{
|
||||
prelude::*,
|
||||
router::{use_route, Link, Route, Router}, // UPDATED
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
fn blog_post(cx: Scope) -> Element {
|
||||
let route = use_route(cx); // NEW
|
||||
let blog_text = "";
|
||||
|
||||
cx.render(rsx! {
|
||||
p { "{blog_text}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
Dioxus Router provides built in methods to extract information from a route. We could utilize the ``segments``, ``nth_segment``, or ``last_segment`` method for our case but we'll use the ``segment`` method which extracts a specific URL parameter.
|
||||
The ``segment`` method also parses the parameter into any type for us. We'll use a match expression that handles a parsing error and on success, uses our helper function to grab the blog post.
|
||||
```rs
|
||||
fn blog_post(cx: Scope) -> Element {
|
||||
let route = use_route(cx);
|
||||
|
||||
// NEW
|
||||
let blog_text = match route.segment::<String>("post").unwrap() {
|
||||
Ok(val) => get_blog_post(&val),
|
||||
Err(_) => "An unknown error occured".to_string(),
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
p { "{blog_text}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
And finally add the ``blog_post`` component to your ``app`` component:
|
||||
```rs
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
self::navbar {}
|
||||
Route { to: "/", self::homepage {}}
|
||||
Route {
|
||||
to: "/blog",
|
||||
p { "-- Dioxus Blog --" }
|
||||
Route { to: "/:post", self::blog_post {} } // UPDATED
|
||||
}
|
||||
Route { to: "", self::page_not_found {}}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
That's it! If you head to ``/blog/foo`` you should see ``Welcome to the foo blog post!``.
|
||||
|
||||
### Conclusion
|
||||
In this chapter we utilized Dioxus Router's Link, URL Parameter, and ``use_route`` functionality to build the blog portion of our application. In the next and final chapter, we will go over the ``Redirect`` component to redirect non-authorized users to another page.
|
|
@ -1,95 +0,0 @@
|
|||
# Creating Our First Route
|
||||
In this chapter, we will continue off of our new Dioxus project to create a homepage and start utilizing Dioxus Router!
|
||||
|
||||
### Fundamentals
|
||||
Dioxus Router works based on a router and route component. If you've ever used [React Router](https://reactrouter.com/), you should feel at home with Dioxus Router.
|
||||
|
||||
To get started, import the ``Router`` and ``Route`` components.
|
||||
```rs
|
||||
use dioxus::{
|
||||
prelude::*,
|
||||
router::{Route, Router}
|
||||
}
|
||||
```
|
||||
We also need an actual page to route to! Add a homepage component:
|
||||
```rs
|
||||
fn homepage(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
p { "Welcome to Dioxus Blog!" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### To Route or Not to Route
|
||||
We want to use Dioxus Router to seperate our application into different "pages". Dioxus Router will then determine which page to render based on the URL path.
|
||||
|
||||
To start using Dioxus Router, we need to use the ``Router`` component.
|
||||
Replace the ``p { "Hello, wasm!" }`` in your ``app`` component with a Router component:
|
||||
```rs
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Router {} // NEW
|
||||
})
|
||||
}
|
||||
```
|
||||
Now we have established a router and we can create our first route. We will be creating a route for our homepage component we created earlier.
|
||||
```rs
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
Route { to: "/", self::homepage {}} // NEW
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
If you head to your application's browser tab, you should see the text ``Welcome to Dioxus Blog!`` when on the root URL (``http://localhost:8080/``). If you enter a different path for the URL, nothing should be displayed.
|
||||
|
||||
This is because we told Dioxus Router to render the ``homepage`` component only when the URL path is ``/``. You can tell Dioxus Router to render any kind of component such as a ``div {}``.
|
||||
|
||||
### What if a Route Doesn't Exist?
|
||||
In our example Dioxus Router doesn't render anything. If we wanted to, we could tell Dioxus Router to render a component all the time! Try it out:
|
||||
```rs
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
p { "-- Dioxus Blog --" } // NEW
|
||||
Route { to: "/", self::homepage {}}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
We will go into more detail about this in the next chapter.
|
||||
|
||||
Many sites also have a "404" page for when a URL path leads to nowhere. Dioxus Router can do this too! Create a new ``page_not_found`` component.
|
||||
```rs
|
||||
fn page_not_found(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
p { "Oops! The page you are looking for doesn't exist!" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Now to tell Dioxus Router to render our new component when no route exists. Create a new route with a path of nothing:
|
||||
```rs
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
p { "-- Dioxus Blog --" }
|
||||
Route { to: "/", self::homepage {}}
|
||||
Route { to: "", self::page_not_found {}} // NEW
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
Now when you go to a route that doesn't exist, you should see the page not found text and the text we told Dioxus Router to render all the time.
|
||||
```
|
||||
// localhost:8080/abc
|
||||
|
||||
-- Dioxus Blog --
|
||||
Oops! The page you are looking for doesn't exist!
|
||||
```
|
||||
|
||||
> Make sure you put your empty route at the bottom or else it'll override any routes below it!
|
||||
|
||||
### Conclusion
|
||||
In this chapter we learned how to create a route and tell Dioxus Router what component to render when the URL path is equal to what we specified. We also created a 404 page to handle when a route doesn't exist. Next, we'll create the blog portion of our site. We will utilize nested routes and URL parameters.
|
|
@ -1,68 +0,0 @@
|
|||
# Getting Started
|
||||
Before we start utilizing Dioxus Router, we need to initialize a Dioxus web application.
|
||||
|
||||
#### Required Tools
|
||||
If you haven't already, make sure you install the [dioxus-cli](https://dioxuslabs.com/nightly/cli/) build tool and the rust ``wasm32-unknown-unknown`` target:
|
||||
```
|
||||
$ cargo install dioxus-cli
|
||||
...
|
||||
$ rustup target add wasm32-unkown-unknown
|
||||
...
|
||||
```
|
||||
|
||||
### Creating the Project
|
||||
First, create a new cargo binary project:
|
||||
```
|
||||
cargo new --bin dioxus-blog
|
||||
```
|
||||
|
||||
Next, we need to add dioxus with the web and router feature to our ``Cargo.toml`` file.
|
||||
```toml
|
||||
[package]
|
||||
name = "dioxus-blog"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
dioxus = { version = "0.1.8", features = ["web", "router"] }
|
||||
```
|
||||
|
||||
Now we can start coding! Create an ``index.html`` file in the root of your project:
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Dioxus Blog</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main"></div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
You can add whatever you want to this file, just ensure that you have a ``div`` with the id of ``main`` in the root of your body element. This is essentially a handle to where Dioxus will render your components.
|
||||
|
||||
Now move to ``src/main.rs`` and replace its contents with:
|
||||
```rs
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// Launch Dioxus web app
|
||||
dioxus_web::launch(app);
|
||||
}
|
||||
|
||||
// Our root component.
|
||||
fn app(cx: Scope) -> Element {
|
||||
// Render "Hello, wasm!" to the screen.
|
||||
cx.render(rsx! {
|
||||
p { "Hello, wasm!"}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Our project is now setup! To make sure everything is running correctly, in the root of your project run:
|
||||
```
|
||||
dioxus serve --platform web
|
||||
```
|
||||
Then head to [http://localhost:8080](http://localhost:8080) in your browser, and you should see ``Hello, wasm!`` on your screen.
|
||||
|
||||
#### Conclusion
|
||||
We setup a new project with Dioxus and got everything running correctly. Next we'll create a small homepage and start our journey with Dioxus Router.
|
|
@ -1,14 +0,0 @@
|
|||
# Dioxus Router: Guide
|
||||
In this guide you'll learn to effectively use Dioxus Router whether you're building a small todo app or the next FAANG company. We will create a small website with a blog, homepage, and more!
|
||||
|
||||
#### You'll learn how to
|
||||
- Create routes and render "pages".
|
||||
- Utilize nested routes, create a navigation bar, and render content for a set of routes.
|
||||
- Gather URL parameters to dynamically display content.
|
||||
- Redirect your visitors wherever you want.
|
||||
|
||||
> Disclaimer
|
||||
>
|
||||
> This site will only display the features of Dioxus Router. It will not include any actual functionality. To keep things simple we will only be using a single file, this is not the recommended way of doing things with a real application.
|
||||
|
||||
You can find the complete application [here](https://github.com/DogeDark/dioxus-router-example).
|
|
@ -1,51 +0,0 @@
|
|||
# Redirection Perfection
|
||||
You're well on your way to becoming a routing master!
|
||||
|
||||
In this chapter we will cover utilizing the ``Redirect`` component so you can take Rickrolling to the next level. We will also provide some optional challenges at the end if you want to continue your practice with not only Dioxus Router but with Dioxus in general.
|
||||
|
||||
### What Is This Redirect Thing?
|
||||
The ``Redirect`` component is simple! When Dioxus determines that it should be rendered, it will redirect your application visitor to wherever you want.
|
||||
In this example, let's say that you added a secret page to your site but didn't have time to program in the permission system. As a quick fix you add a redirect.
|
||||
|
||||
As always, let's first create a new component named ``secret_page``.
|
||||
```rs
|
||||
fn secret_page(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
p { "This page is not to be viewed!" }
|
||||
})
|
||||
}
|
||||
```
|
||||
To redirect our visitors, all we have to do is render the ``Redirect`` component. The ``Redirect`` component is very similar to the ``Link`` component. The main difference is it doesn't display anything new.
|
||||
First import the ``Redirect`` component and then update your ``secret_page`` component:
|
||||
```rs
|
||||
use dioxus::{
|
||||
prelude::*,
|
||||
router::{use_route, Link, Redirect, Route, Router}, // UPDATED
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
fn secret_page(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
p { "This page is not to be viewed!" }
|
||||
Redirect { to: "/" } // NEW
|
||||
})
|
||||
}
|
||||
```
|
||||
That's it! Now your users will be redirected away from the secret page.
|
||||
|
||||
>Similar to the ``Link`` component, the ``Redirect`` component needs to be explicitly set to redirect to an external site. To link to external sites, add the ``external: true`` property.
|
||||
>```rs
|
||||
>Redirect { to: "https://github.com", external: true}
|
||||
>```
|
||||
|
||||
### Conclusion
|
||||
Well done! You've completed the Dioxus Router guide book. You've built a small application and learned about the many things you can do with Dioxus Router. To continue your journey, you can find a list of challenges down below, or you can check out the [reference](../reference/index.md).
|
||||
|
||||
### Challenges
|
||||
- Organize your components into seperate files for better maintainability.
|
||||
- Give your app some style if you haven't already.
|
||||
- Build an about page so your visitors know who you are.
|
||||
- Add a user system that uses URL parameters.
|
||||
- Create a simple admin system to create, delete, and edit blogs.
|
||||
- If you want to go to the max, hook up your application to a rest API and database.
|
41
docs/router/src/index.md
Normal file
41
docs/router/src/index.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Introduction
|
||||
Whether or not you are building a website, desktop app, or mobile app,
|
||||
splitting your app's views into "pages" can be an effective method for
|
||||
organization and maintainability.
|
||||
|
||||
For this purpose, Dioxus provides a router. To start utilizing it, add it as a
|
||||
dependency in your `Cargo.toml` file:
|
||||
```toml
|
||||
[dependencies]
|
||||
# use this for native apps
|
||||
dioxus-router = "*"
|
||||
#use this for web apps
|
||||
dioxus-router = { version = "*", features = ["web"] }
|
||||
|
||||
# in both cases replace * with the current version
|
||||
```
|
||||
|
||||
You can also use the `cargo` command to add the dependency:
|
||||
```sh
|
||||
$ cargo add dioxus-router
|
||||
$ cargo add dioxus-router --features web
|
||||
```
|
||||
|
||||
> If you are not familiar with Dioxus itself, check out the [Dioxus book][db]
|
||||
> first.
|
||||
|
||||
This book is intended to get you up to speed with Dioxus Router. It is split
|
||||
into two sections:
|
||||
1. The [Features](./features/index.md) part explains individual features in
|
||||
depth. You can read it 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 ouf the
|
||||
_[example project](./example/introduction.md)_. It guides you through
|
||||
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
|
||||
> can also check out the [API Docs][api].
|
||||
|
||||
[api]: https://docs.rs/dioxus-router/
|
||||
[db]: https://dioxuslabs.com/guide/
|
|
@ -1,2 +0,0 @@
|
|||
# Dioxus Router: Reference
|
||||
This section includes a reference to Dioxus Router's API and functionality.
|
Loading…
Reference in a new issue