()
- .map(|name| name.clone())
- .unwrap_or(String::from("world"));
-
- render! {
- p { "Hello, {name}!" }
- }
-}
-
-fn App(cx: Scope) -> Element {
- let routes = use_router(
- cx,
- &|| RouterConfiguration {
- # synchronous: true,
- # history: Box::new(MemoryHistory::with_initial_path("/Dioxus").unwrap()),
- ..Default::default()
- },
- &|| Segment::empty().catch_all((comp(Greeting), Name { }))
- );
- // ...
- render! {
- Outlet { }
- }
-}
-#
-# let mut vdom = VirtualDom::new(App);
-# vdom.rebuild();
-# assert_eq!(
-# dioxus_ssr::render(&vdom),
-# "Hello, Dioxus!
"
-# );
-```
-
-[`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
diff --git a/docs/router/src/reference/routes/fallback.md b/docs/router/src/reference/routes/fallback.md
deleted file mode 100644
index 954c47317..000000000
--- a/docs/router/src/reference/routes/fallback.md
+++ /dev/null
@@ -1,140 +0,0 @@
-# Fallback Routes
-
-Sometimes the router might be unable to find a route for the provided path. We
-might want it to show a prepared error message to our users in that case.
-Fallback routes allow us to do that.
-
-> This is especially important for use cases where users can manually change the
-> path, like web apps running in the browser.
-
-## A single global fallback
-
-To catch all cases of invalid paths within our app, we can simply add a fallback
-route to our root [`Segment`].
-
-```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-use dioxus::prelude::*;
-# extern crate dioxus_router;
-use dioxus_router::{history::MemoryHistory, prelude::*};
-# extern crate dioxus_ssr;
-
-fn Index(cx: Scope) -> Element {
- render! {
- h1 { "Index" }
- }
-}
-
-fn Fallback(cx: Scope) -> Element {
- render! {
- h1 { "Error 404 - Not Found" }
- p { "The page you asked for doesn't exist." }
- }
-}
-
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- # synchronous: true,
- # history: Box::new(MemoryHistory::with_initial_path("/invalid").unwrap()),
- ..Default::default()
- },
- &|| {
- Segment::content(comp(Index)).fallback(comp(Fallback))
- }
- );
-
- render! {
- Outlet { }
- }
-}
-#
-# let mut vdom = VirtualDom::new(App);
-# vdom.rebuild();
-# assert_eq!(
-# dioxus_ssr::render(&vdom),
-# "Error 404 - Not Found
The page you asked for doesn't exist.
"
-# );
-```
-
-## More specific fallback routes
-
-In some cases we might want to show different fallback content depending on what
-section of our app the user is in.
-
-For example, our app might have several settings pages under `/settings`, such
-as the password settings `/settings/password` or the privacy settings
-`/settings/privacy`. When our user is in the settings section, we want to show
-them _"settings not found"_ instead of _"page not found"_.
-
-We can easily do that by setting a fallback route on our nested [`Segment`]. It
-will then replace the global fallback whenever our [`Segment`] was active.
-
-Note the `.clear_fallback(false)` part. If we didn't add this, the fallback
-content would be rendered inside the `Settings` component.
-
-```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-use dioxus::prelude::*;
-# extern crate dioxus_router;
-use dioxus_router::{history::MemoryHistory, prelude::*};
-# extern crate dioxus_ssr;
-
-// This example doesn't show the index or settings components. It only shows how
-// to set up several fallback routes.
-# fn Index(cx: Scope) -> Element { unimplemented!() }
-# fn Settings(cx: Scope) -> Element { unimplemented!() }
-# fn GeneralSettings(cx: Scope) -> Element { unimplemented!() }
-# fn PasswordSettings(cx: Scope) -> Element { unimplemented!() }
-# fn PrivacySettings(cx: Scope) -> Element { unimplemented!() }
-
-fn GlobalFallback(cx: Scope) -> Element {
- render! {
- h1 { "Error 404 - Page Not Found" }
- }
-}
-
-fn SettingsFallback(cx: Scope) -> Element {
- render! {
- h1 { "Error 404 - Settings Not Found" }
- }
-}
-
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- # synchronous: true,
- # history: Box::new(MemoryHistory::with_initial_path("/settings/invalid").unwrap()),
- ..Default::default()
- },
- &|| {
- Segment::empty()
- .fixed("settings", Route::content(comp(Settings)).nested(
- Segment::content(comp(GeneralSettings))
- .fixed("password", comp(PasswordSettings))
- .fixed("privacy", comp(PrivacySettings))
- .fallback(comp(SettingsFallback))
- .clear_fallback(true)
- ))
- .fallback(comp(GlobalFallback))
- }
- );
-
- render! {
- Outlet { }
- }
-}
-#
-# let mut vdom = VirtualDom::new(App);
-# vdom.rebuild();
-# assert_eq!(
-# dioxus_ssr::render(&vdom),
-# "Error 404 - Settings Not Found
"
-# );
-```
-
-[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
diff --git a/docs/router/src/reference/routes/index.md b/docs/router/src/reference/routes/index.md
index 2798e12d2..7cd0fe9e5 100644
--- a/docs/router/src/reference/routes/index.md
+++ b/docs/router/src/reference/routes/index.md
@@ -1,143 +1,65 @@
# Defining Routes
-When creating a router we need to pass it a [`Segment`]. It tells the router
-about all the routes of our app.
+When creating a [`Routable`] enum, we can define routes for our application using the `route("path")` attribute.
-## Example content
+## Route Segments
-To get a good understanding of how we define routes we first need to prepare
-some example content, so we can see the routing in action.
+Each route is made up of segments. Most segments are separated by `/` characters in the path.
+
+There are four fundamental types of segments:
+
+1. [Static segments](#static-segments) are fixed strings that must be present in the path.
+2. [Dynamic segments](#dynamic-segments) are types that can be parsed from a segment.
+3. [Catch-all segments](#catch-all-segments) are types that can be parsed from multiple segments.
+4. [Query segments](#query-segments) are types that can be parsed from the query string.
+
+Routes are matched:
+
+- First, from most specific to least specific (Static then Dynamic then Catch All) (Query is always matched)
+- Then, if multiple routes match the same path, the order in which they are defined in the enum is followed.
+
+## Static segments
+
+Fixed routes match a specific path. For example, the route `#[route("/about")]` will match the path `/about`.
```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-use dioxus::prelude::*;
-# extern crate dioxus_router;
-use dioxus_router::prelude::*;
-
-fn Index(cx: Scope) -> Element {
- render! {
- h1 { "Welcome to our test site!" }
- }
-}
-
-fn Other(cx: Scope) -> Element {
- render! {
- p { "some other content" }
- }
-}
+{{#include ../../../examples/static_segments.rs:route}}
```
-## Index routes
+## Dynamic Segments
-The easiest thing to do is to define an index route.
+Dynamic segments are in the form of `:name` where `name` is
+the name of the field in the route variant. If the segment is parsed
+successfully then the route matches, otherwise the matching continues.
-Index routes act very similar to `index.html` files in most web servers. They
-are active, when we don't specify a route.
-
-> Note that we wrap our `Index` component with [`comp`]. This is because of
-> rust type system requirements.
+The segment can be of any type that implements `FromStr`.
```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-# use dioxus::prelude::*;
-# extern crate dioxus_router;
-# use dioxus_router::prelude::*;
-# fn Index(cx: Scope) -> Element { unimplemented!() }
-#
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- ..Default::default()
- },
- &|| Segment::content(comp(Index))
- );
-
- // ...
- # unimplemented!()
-}
+{{#include ../../../examples/dynamic_segments.rs:route}}
```
-## Fixed routes
+## Catch All Segments
-It is almost as easy to define a fixed route.
+Catch All segments are in the form of `:...name` where `name` is the name of the field in the route variant. If the segments are parsed successfully then the route matches, otherwise the matching continues.
-Fixed routes work similar to how web servers treat files. They are active, when
-specified in the path. In the example, the path must be `/other`.
+The segment can be of any type that implements `FromSegments`. (Vec implements this by default)
-> The path will be URL decoded before checking if it matches our route.
+Catch All segments must be the _last route segment_ in the path (query segments are not counted) and cannot be included in nests.
```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-# use dioxus::prelude::*;
-# extern crate dioxus_router;
-# use dioxus_router::prelude::*;
-# fn Index(cx: Scope) -> Element { unimplemented!() }
-# fn Other(cx: Scope) -> Element { unimplemented!() }
-#
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- ..Default::default()
- },
- &|| Segment::content(comp(Index)).fixed("other", comp(Other))
- // ^ note the absence of a / prefix
- );
-
- // ...
- # unimplemented!()
-}
+{{#include ../../../examples/catch_all_segments.rs:route}}
```
-## Full Code
+## Query Segments
+
+Query segments are in the form of `?:name` where `name` is the name of the field in the route variant.
+
+Unlike [Dynamic Segments](#dynamic-segments) and [Catch All Segments](#catch-all-segments), parsing a Query segment must not fail.
+
+The segment can be of any type that implements `FromQuery`.
+
+Query segments must be the _after all route segments_ and cannot be included in nests.
```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-use dioxus::prelude::*;
-# extern crate dioxus_router;
-use dioxus_router::{history::MemoryHistory, prelude::*};
-# extern crate dioxus_ssr;
-
-fn Index(cx: Scope) -> Element {
- render! {
- h1 { "Welcome to our test site!" }
- }
-}
-
-fn Other(cx: Scope) -> Element {
- render! {
- p { "some other content" }
- }
-}
-
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- # synchronous: true,
- # history: Box::new(MemoryHistory::with_initial_path("/other").unwrap()),
- ..Default::default()
- },
- &|| Segment::content(comp(Index)).fixed("other", comp(Other))
- );
-
- render! {
- Outlet { }
- }
-}
-#
-# let mut vdom = VirtualDom::new(App);
-# vdom.rebuild();
-# assert_eq!(
-# dioxus_ssr::render(&vdom),
-# "some other content
"
-# );
+{{#include ../../../examples/query_segments.rs:route}}
```
-
-[`comp`]: https://docs.rs/dioxus-router/latest/dioxus_router/prelude/fn.comp.html
-[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
diff --git a/docs/router/src/reference/routes/matching.md b/docs/router/src/reference/routes/matching.md
deleted file mode 100644
index 2f617d1b9..000000000
--- a/docs/router/src/reference/routes/matching.md
+++ /dev/null
@@ -1,139 +0,0 @@
-# Matching Routes
-
-> Make sure you understand how [catch all routes](./catch_all.md) work before
-> reading this page.
-
-When accepting parameters via the path, some complex applications might need to
-decide what route should be active based on the format of that parameter.
-_Matching_ routes make it easy to implement such behavior.
-
-> The parameter will be URL decoded, both for checking if the route is active
-> and when it is provided to the application.
-
-> The example below is only for showing _matching route_ functionality. It is
-> unfit for all other purposes.
-
-## Code Example
-
-> Notice that the parameter of a _matching route_ has the same type as a
-> [_catch all route_](./catch_all.md).
-
-```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-# use dioxus::prelude::*;
-# extern crate dioxus_router;
-# use dioxus_router::{history::MemoryHistory, prelude::*};
-# extern crate dioxus_ssr;
-# extern crate regex;
-use regex::Regex;
-
-struct Name;
-
-fn GreetingFemale(cx: Scope) -> Element {
- let route = use_route(cx).unwrap();
- let name = route.parameter::()
- .map(|name| {
- let mut name = name.to_string();
- name.remove(0);
- name
- })
- .unwrap_or(String::from("Anonymous"));
-
- render! {
- p { "Hello Mrs. {name}" }
- }
-}
-
-fn GreetingMale(cx: Scope) -> Element {
- let route = use_route(cx).unwrap();
- let name = route.parameter::()
- .map(|name| {
- let mut name = name.to_string();
- name.remove(0);
- name
- })
- .unwrap_or(String::from("Anonymous"));
-
- render! {
- p { "Hello Mr. {name}" }
- }
-}
-
-fn GreetingWithoutGender(cx: Scope) -> Element {
- let route = use_route(cx).unwrap();
- let name = route.parameter::()
- .map(|name| name.to_string())
- .unwrap_or(String::from("Anonymous"));
-
- render! {
- p { "Hello {name}" }
- }
-}
-
-fn GreetingKenobi(cx: Scope) -> Element {
- render! {
- p { "Hello there." }
- p { "General Kenobi." }
- }
-}
-
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- # synchronous: true,
- # history: Box::new(MemoryHistory::with_initial_path("/fAnna").unwrap()),
- ..Default::default()
- },
- &|| {
- Segment::empty()
- .fixed("kenobi", comp(GreetingKenobi))
- .matching(
- Regex::new("^f").unwrap(),
- ParameterRoute::content::(comp(GreetingFemale))
- )
- .matching(
- Regex::new("^m").unwrap(),
- (comp(GreetingMale), Name { })
- )
- .catch_all((comp(GreetingWithoutGender), Name { }))
- }
- );
-
- render! {
- Outlet { }
- }
-}
-#
-# let mut vdom = VirtualDom::new(App);
-# vdom.rebuild();
-# let html = dioxus_ssr::render(&vdom);
-# assert_eq!(html, "Hello Mrs. Anna
");
-```
-
-## 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
diff --git a/docs/router/src/reference/routes/multiple-and-redirect.md b/docs/router/src/reference/routes/multiple-and-redirect.md
deleted file mode 100644
index a5a17a84a..000000000
--- a/docs/router/src/reference/routes/multiple-and-redirect.md
+++ /dev/null
@@ -1,63 +0,0 @@
-# Multiple Components & Redirects
-
-## Multiple Components
-
-When creating complex apps we sometimes want to have multiple pieces of content
-side by side. The router allows us to do this. For more details see the section
-about [named `Outlet`s](../outlets.md#named-outlets).
-
-## Redirects
-
-In some cases we may want to redirect our users to another page whenever they
-open a specific path. We can tell the router to do this when defining our
-routes.
-
-> Redirects to external pages only work in certain conditions. For more details
-> see the chapter about [external navigation failures](../failures/external.md).
-
-In the following example we will redirect everybody from `/` and `/start` to
-`/home`.
-
-```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-use dioxus::prelude::*;
-# extern crate dioxus_router;
-use dioxus_router::{history::MemoryHistory, prelude::*};
-# extern crate dioxus_ssr;
-
-fn Home(cx: Scope) -> Element {
- render! {
- h1 { "Home Page" }
- }
-}
-
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- # synchronous: true,
- # history: Box::new(MemoryHistory::with_initial_path("/home").unwrap()),
- ..Default::default()
- },
- &|| {
- Segment::content(comp(Home))
- // notice that we use RouteContent::Redirect instead of
- // RouteContent::Content (which we have been using indirectly)
- .fixed(
- "home",
- RouteContent::Redirect(NavigationTarget::Internal("/".into()))
- )
- .fixed("start", "/") // short form
- });
-
- render! {
- Outlet { }
- }
-}
-#
-# let mut vdom = VirtualDom::new(App);
-# vdom.rebuild();
-# let html = dioxus_ssr::render(&vdom);
-# assert_eq!(html, "Home Page
");
-```
diff --git a/docs/router/src/reference/routes/nested.md b/docs/router/src/reference/routes/nested.md
index e19d5917f..4860fbb0d 100644
--- a/docs/router/src/reference/routes/nested.md
+++ b/docs/router/src/reference/routes/nested.md
@@ -19,199 +19,21 @@ We might want to map this structure to these paths and components:
/settings/privacy -> Settings { PrivacySettings }
```
-Nested routes allow us to do this.
+Nested routes allow us to do this without repeating /settings in every route.
-## Route Depth
+## Nesting
-With nesting routes, the router manages content on multiple levels. In our
-example, when the path is `/settings`, there are two levels of content:
+To nest routes, we use the `#[nest("path")]` and `#[end_nest]` attributes.
-0. The `Settings` component
-1. The `GeneralSettings` component
+The path in nest must not:
-Dioxus Router uses the [`Outlet`] component to actually render content, but each
-[`Outlet`] can only render content from one level. This means that for the
-content of nested routes to actually be rendered, we also need nested
-[`Outlet`]s.
+1. Contain a [Catch All Segment](index.md#catch-all-segments)
+2. Contain a [Query Segment](index.md#query-segments)
-## Defining the content components
+If you define a dynamic segment in a nest, it will be available to all child routes and layouts.
-We start by creating the components we want the router to render.
-
-Take a look at the `Settings` component. When it gets rendered by an [`Outlet`],
-it will render a second [`Outlet`]. Thus the second [`Outlet`] is nested within
-the first one, and will in turn render our nested content.
+To finish a nest, we use the `#[end_nest]` attribute or the end of the enum.
```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-# use dioxus::prelude::*;
-# extern crate dioxus_router;
-# use dioxus_router::prelude::*;
-#
-fn Settings(cx: Scope) -> Element {
- render! {
- h1 { "Settings" }
- Outlet { }
- }
-}
-
-fn GeneralSettings(cx: Scope) -> Element {
- render! {
- h2 { "General Settings" }
- }
-}
-
-fn PWSettings(cx: Scope) -> Element {
- render! {
- h2 { "Password Settings" }
- }
-}
-
-fn PrivacySettings(cx: Scope) -> Element {
- render! {
- h2 { "Privacy Settings" }
- }
-}
+{{#include ../../../examples/nest.rs:route}}
```
-
-## Defining the root [`Segment`]
-
-Now we create the [`Segment`] that we will pass to the router.
-
-Note that we wrap `comp(Settings)` within a [`Route`]. For this exact code that
-is unnecessary, as this would be done automatically. However, in the next step
-we'll use a method of [`Route`], so we might as well add this now.
-
-```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-# use dioxus::prelude::*;
-# extern crate dioxus_router;
-# use dioxus_router::prelude::*;
-# fn Settings(cx: Scope) -> Element { unimplemented!() }
-#
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- ..Default::default()
- },
- &|| Segment::empty().fixed("settings", Route::content(comp(Settings)))
- );
-
- // ...
- # unimplemented!()
-}
-```
-
-## Defining the nested [`Segment`]
-
-In order to create nested routes we need to create a nested [`Segment`]. We then
-pass it to the [`Route`] on the root segment.
-
-> A [`Segment`] always refers to one exact segment of the path.
->
-> https://router.example/`root_segment`/`first_nested_segment`/`second_nested_segment`/...
-
-```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-# use dioxus::prelude::*;
-# extern crate dioxus_router;
-# use dioxus_router::prelude::*;
-# fn Settings(cx: Scope) -> Element { unimplemented!() }
-# fn GeneralSettings(cx: Scope) -> Element { unimplemented!() }
-# fn PWSettings(cx: Scope) -> Element { unimplemented!() }
-# fn PrivacySettings(cx: Scope) -> Element { unimplemented!() }
-#
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- ..Default::default()
- },
- &|| Segment::empty().fixed(
- "settings",
- Route::content(comp(Settings)).nested(
- Segment::content(comp(GeneralSettings))
- .fixed("password", comp(PWSettings))
- .fixed("privacy", comp(PrivacySettings))
- )
- )
- );
-
- // ...
- # unimplemented!()
-}
-```
-
-## Full Code
-
-```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-# use dioxus::prelude::*;
-# extern crate dioxus_router;
-# use dioxus_router::{history::MemoryHistory, prelude::*};
-# extern crate dioxus_ssr;
-#
-fn Settings(cx: Scope) -> Element {
- render! {
- h1 { "Settings" }
- Outlet { }
- }
-}
-
-fn GeneralSettings(cx: Scope) -> Element {
- render! {
- h2 { "General Settings" }
- }
-}
-
-fn PWSettings(cx: Scope) -> Element {
- render! {
- h2 { "Password Settings" }
- }
-}
-
-fn PrivacySettings(cx: Scope) -> Element {
- render! {
- h2 { "Privacy Settings" }
- }
-}
-
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- # synchronous: true,
- # history: Box::new(MemoryHistory::with_initial_path("/settings/privacy").unwrap()),
- ..Default::default()
- },
- &|| Segment::empty().fixed(
- "settings",
- Route::content(comp(Settings)).nested(
- Segment::content(comp(GeneralSettings))
- .fixed("password", comp(PWSettings))
- .fixed("privacy", comp(PrivacySettings))
- )
- )
- );
-
- render! {
- Outlet { }
- }
-}
-#
-# let mut vdom = VirtualDom::new(App);
-# vdom.rebuild();
-# assert_eq!(
-# dioxus_ssr::render(&vdom),
-# "Settings
Privacy Settings
"
-# );
-```
-
-[`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
diff --git a/docs/router/src/reference/routing-update-callback.md b/docs/router/src/reference/routing-update-callback.md
index 93368d830..f4fdb9a99 100644
--- a/docs/router/src/reference/routing-update-callback.md
+++ b/docs/router/src/reference/routing-update-callback.md
@@ -1,13 +1,12 @@
# Routing Update Callback
-In some cases we might want to run custom code when the current route changes.
-For this reason, the [`RouterConfiguration`] exposes an `on_update` field.
+In some cases, we might want to run custom code when the current route changes.
+For this reason, the [`RouterConfig`] exposes an `on_update` field.
## How does the callback behave?
The `on_update` is called whenever the current routing information changes. It
-is called after the router updated its internal state, but before depended
-components and hooks are updated.
+is called after the router updated its internal state, but before dependent components and hooks are updated.
If the callback returns a [`NavigationTarget`], the router will replace the
current location with the specified target. It will not call the
@@ -16,54 +15,11 @@ current location with the specified target. It will not call the
If at any point the router encounters a
[navigation failure](./failures/index.md), it will go to the appropriate state
without calling the `on_update`. It doesn't matter if the invalid target
-initiated the navigation, was found as a redirect target or returned by the
+initiated the navigation, was found as a redirect target, or was returned by the
`on_update` itself.
## Code Example
```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-# extern crate dioxus_router;
-# extern crate dioxus_ssr;
-#
-use std::sync::Arc;
-
-use dioxus::prelude::*;
-use dioxus_router::prelude::*;
-
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- # synchronous: true,
- on_update: Some(Arc::new(|state| -> Option {
- 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), "Some content
");
+{{#include ../../examples/routing_update.rs:router}}
```
-
-[`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html
-[`RouterConfiguration`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/struct.RouterConfiguration.html
diff --git a/docs/router/src/reference/sitemap-generation.md b/docs/router/src/reference/sitemap-generation.md
deleted file mode 100644
index c77235b19..000000000
--- a/docs/router/src/reference/sitemap-generation.md
+++ /dev/null
@@ -1,225 +0,0 @@
-# Sitemap Generation
-
-If you need a list of all routes you have defined (e.g. for statically
-generating all pages), Dioxus Router provides functions to extract that
-information from a [`Segment`].
-
-## Preparing an app
-
-We will start by preparing an app with some routes like we normally would.
-
-```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-use dioxus::prelude::*;
-# extern crate dioxus_router;
-use dioxus_router::{history::MemoryHistory, prelude::*};
-# extern crate dioxus_ssr;
-
-fn Home(cx: Scope) -> Element {
- render! {
- h1 { "Home" }
- }
-}
-
-fn Fixed(cx: Scope) -> Element {
- render! {
- h1 { "Fixed" }
- Outlet { }
- }
-}
-
-fn Nested(cx: Scope) -> Element {
- render! {
- h2 { "Nested" }
- }
-}
-
-struct ParameterName;
-fn Parameter(cx: Scope) -> Element {
- let route = use_route(cx).unwrap();
- let param = route.parameter::().unwrap_or_default();
-
- render! {
- h1 { "Parameter: {param}" }
- }
-}
-
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- # synchronous: true,
- history: Box::new(MemoryHistory::with_initial_path("/fixed/nested").unwrap()),
- ..Default::default()
- },
- &|| {
- Segment::content(comp(Home))
- .fixed(
- "fixed",
- Route::content(comp(Fixed)).nested(
- Segment::empty().fixed("nested", comp(Nested))
- )
- )
- .catch_all((comp(Parameter), ParameterName { }))
- }
- );
-
- render! {
- Outlet { }
- }
-}
-#
-# let mut vdom = VirtualDom::new(App);
-# vdom.rebuild();
-# assert_eq!(dioxus_ssr::render(&mut vdom), "Fixed
Nested
");
-```
-
-## Modifying the app to make using sitemaps easier
-
-Preparing our app for sitemap generation is quite easy. We just need to extract
-our segment definition into its own function.
-
-```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-# use dioxus::prelude::*;
-# extern crate dioxus_router;
-# use dioxus_router::prelude::*;
-# extern crate dioxus_ssr;
-# fn Home(cx: Scope) -> Element { unimplemented!() }
-# fn Fixed(cx: Scope) -> Element { unimplemented!() }
-# fn Nested(cx: Scope) -> Element { unimplemented!() }
-# struct ParameterName;
-# fn Parameter(cx: Scope) -> Element { unimplemented!() }
-#
-fn App(cx: Scope) -> Element {
- use_router(
- cx,
- &|| RouterConfiguration {
- ..Default::default()
- },
- &prepare_routes
- );
-
- render! {
- Outlet { }
- }
-}
-
-fn prepare_routes() -> Segment {
- Segment::content(comp(Home))
- .fixed(
- "fixed",
- Route::content(comp(Fixed)).nested(
- Segment::empty().fixed("nested", comp(Nested))
- )
- )
- .catch_all((comp(Parameter), ParameterName { }))
-}
-```
-
-## Sitemaps with parameter names
-
-The first variant to generate sitemaps is very simple. It finds all routes
-within the [`Segment`] and adds them to the returned `Vec`.
-
-Matching and parameter routes are represented by their `key`, prefixed with `\`.
-Besides that `\`, all paths are URL encoded.
-
-```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-# extern crate dioxus;
-# use dioxus::prelude::*;
-# extern crate dioxus_router;
-# use dioxus_router::prelude::*;
-# extern crate dioxus_ssr;
-# fn Home(cx: Scope) -> Element { unimplemented!() }
-# fn Fixed(cx: Scope) -> Element { unimplemented!() }
-# fn Nested(cx: Scope) -> Element { unimplemented!() }
-# struct ParameterName;
-# fn Parameter(cx: Scope) -> Element { unimplemented!() }
-# fn prepare_routes() -> Segment {
-# Segment::content(comp(Home))
-# .fixed(
-# "fixed",
-# Route::content(comp(Fixed)).nested(
-# Segment::empty().fixed("nested", comp(Nested))
-# )
-# )
-# .catch_all((comp(Parameter), ParameterName { }))
-# }
-
-let expected = vec![
- "/",
- "/fixed",
- "/fixed/nested",
- // Usually, here would be a fourth result representing the parameter route.
- // However, due to mdbook the name for this file would constantly change,
- // which is why we cannot show it. It would look something like this:
- // "/\\your_crate::ParameterName",
-];
-let mut sitemap = prepare_routes().gen_sitemap();
-sitemap.remove(3); // see above
-assert_eq!(sitemap, expected);
-```
-
-## Sitemaps with actual parameter values
-
-The second variant to generate sitemaps is a bit more involved. When it
-encounters a parameter route, it inserts all values with a matching `key` that
-were provided to it.
-
-Matching routes only add their path if the value matches their regex.
-
-All paths are URL encoded.
-
-```rust, no_run
-# // Hidden lines (like this one) make the documentation tests work.
-use std::collections::{BTreeMap, HashSet};
-# extern crate dioxus;
-# use dioxus::prelude::*;
-# extern crate dioxus_router;
-# use dioxus_router::prelude::*;
-# extern crate dioxus_ssr;
-# fn Home(cx: Scope) -> Element { unimplemented!() }
-# fn Fixed(cx: Scope) -> Element { unimplemented!() }
-# fn Nested(cx: Scope) -> Element { unimplemented!() }
-# struct ParameterName;
-# fn Parameter(cx: Scope) -> Element { unimplemented!() }
-# fn prepare_routes() -> Segment {
-# 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::(),
- vec![
- String::from("some-parameter-value"),
- String::from("other-parameter-value")
- ]
- );
-
- parameters
-};
-
-let expected: Vec = 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
diff --git a/docs/router/src/reference/static-generation.md b/docs/router/src/reference/static-generation.md
new file mode 100644
index 000000000..c43f9fe4b
--- /dev/null
+++ b/docs/router/src/reference/static-generation.md
@@ -0,0 +1,15 @@
+# Static Generation
+
+## Getting the Sitemap
+
+The [`Routable`] trait includes an associated [`SITE_MAP`] constant that contains the map of all of the routes in the enum.
+
+By default, the sitemap is a tree of (static or dynamic) RouteTypes, but it can be flattened into a list of individual routes with the `.flatten()` method.
+
+## Generating a Sitemap
+
+To statically render pages, we need to flatten the route tree and generate a file for each route that contains only static segments:
+
+```rust, no_run
+{{#include ../../../../packages/router/examples/static_generation.rs}}
+```
diff --git a/packages/router/examples/static_generation.rs b/packages/router/examples/static_generation.rs
new file mode 100644
index 000000000..249deb28b
--- /dev/null
+++ b/packages/router/examples/static_generation.rs
@@ -0,0 +1,115 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use dioxus_router::prelude::*;
+use std::io::prelude::*;
+use std::{path::PathBuf, str::FromStr};
+
+fn main() {
+ render_static_pages();
+}
+
+fn render_static_pages() {
+ for route in Route::SITE_MAP
+ .iter()
+ .flat_map(|seg| seg.flatten().into_iter())
+ {
+ // check if this is a static segment
+ let mut file_path = PathBuf::from("./");
+ let mut full_path = String::new();
+ let mut is_static = true;
+ for segment in &route {
+ match segment {
+ SegmentType::Static(s) => {
+ file_path.push(s);
+ full_path += "/";
+ full_path += s;
+ }
+ _ => {
+ // skip routes with any dynamic segments
+ is_static = false;
+ break;
+ }
+ }
+ }
+
+ if is_static {
+ let route = Route::from_str(&full_path).unwrap();
+ let mut vdom = VirtualDom::new_with_props(RenderPath, RenderPathProps { path: route });
+ let _ = vdom.rebuild();
+
+ file_path.push("index.html");
+ std::fs::create_dir_all(file_path.parent().unwrap()).unwrap();
+ let mut file = std::fs::File::create(file_path).unwrap();
+
+ let body = dioxus_ssr::render(&vdom);
+ let html = format!(
+ r#"
+
+
+
+
+
+ {}
+
+
+ {}
+
+
+"#,
+ full_path, body
+ );
+ file.write_all(html.as_bytes()).unwrap();
+ }
+ }
+}
+
+#[inline_props]
+fn RenderPath(cx: Scope, path: Route) -> Element {
+ let path = path.clone();
+ render! {
+ Router {
+ config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
+ }
+ }
+}
+
+#[inline_props]
+fn Blog(cx: Scope) -> Element {
+ render! {
+ div {
+ "Blog"
+ }
+ }
+}
+
+#[inline_props]
+fn Post(cx: Scope) -> Element {
+ render! {
+ div {
+ "Post"
+ }
+ }
+}
+
+#[inline_props]
+fn Home(cx: Scope) -> Element {
+ render! {
+ div {
+ "Home"
+ }
+ }
+}
+
+#[rustfmt::skip]
+#[derive(Clone, Debug, PartialEq, Routable)]
+enum Route {
+ #[nest("/blog")]
+ #[route("/")]
+ Blog {},
+ #[route("/post")]
+ Post {},
+ #[end_nest]
+ #[route("/")]
+ Home {},
+}
diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs
index 4beeac5de..9db86db08 100644
--- a/packages/router/src/lib.rs
+++ b/packages/router/src/lib.rs
@@ -7,7 +7,7 @@ pub mod navigation;
pub mod routable;
/// Components interacting with the router.
-mod components {
+pub mod components {
mod default_errors;
pub use default_errors::*;
@@ -37,7 +37,7 @@ mod router_cfg;
mod history;
/// Hooks for interacting with the router in components.
-mod hooks {
+pub mod hooks {
mod use_router;
pub(crate) use use_router::*;