diff --git a/blog.html b/blog.html new file mode 100644 index 000000000..e69de29bb diff --git a/docs/router/examples/catch_all_segments.rs b/docs/router/examples/catch_all_segments.rs new file mode 100644 index 000000000..d4fcb5261 --- /dev/null +++ b/docs/router/examples/catch_all_segments.rs @@ -0,0 +1,24 @@ +#![allow(non_snake_case, unused)] +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +// ANCHOR: route +#[derive(Routable, Clone)] +#[rustfmt::skip] +enum Route { + // segments that start with :... are catch all segments + #[route("/blog/:...segments")] + BlogPost { + // You must include catch all segment in child variants + segments: Vec, + }, +} + +// Components must contain the same catch all segments as their corresponding variant +#[inline_props] +fn BlogPost(cx: Scope, segments: Vec) -> Element { + todo!() +} +// ANCHOR_END: route + +fn main() {} diff --git a/docs/router/examples/dynamic_segments.rs b/docs/router/examples/dynamic_segments.rs new file mode 100644 index 000000000..6aee22e6c --- /dev/null +++ b/docs/router/examples/dynamic_segments.rs @@ -0,0 +1,35 @@ +#![allow(non_snake_case, unused)] +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +// ANCHOR: route +#[derive(Routable, Clone)] +#[rustfmt::skip] +enum Route { + // segments that start with : are dynamic segments + #[route("/blog/:name")] + BlogPost { + // You must include dynamic segments in child variants + name: String, + }, + #[route("/document/:id")] + Document { + // You can use any type that implements FromStr + // If the segment can't be parsed, the route will not match + id: usize, + }, +} + +// Components must contain the same dynamic segments as their corresponding variant +#[inline_props] +fn BlogPost(cx: Scope, name: String) -> Element { + todo!() +} + +#[inline_props] +fn Document(cx: Scope, id: usize) -> Element { + todo!() +} +// ANCHOR_END: route + +fn main() {} diff --git a/docs/router/examples/first_route.rs b/docs/router/examples/first_route.rs index 6d61317ee..d13bef93d 100644 --- a/docs/router/examples/first_route.rs +++ b/docs/router/examples/first_route.rs @@ -9,6 +9,7 @@ enum Route { // The home page is at the / route #[route("/")] // If the name of the component and variant are the same you can omit the component and props name + // If they are different you can specify them like this: // #[route("/", ComponentName, PropsName)] Home {}, } diff --git a/docs/router/examples/history_buttons.rs b/docs/router/examples/history_buttons.rs new file mode 100644 index 000000000..971549aa5 --- /dev/null +++ b/docs/router/examples/history_buttons.rs @@ -0,0 +1,30 @@ +#![allow(non_snake_case, unused)] +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +#[derive(Routable, Clone)] +#[rustfmt::skip] +enum Route { + #[route("/")] + Home {}, +} + +#[inline_props] +fn Home(cx: Scope) -> Element { + todo!() +} + +// ANCHOR: history_buttons +fn HistoryNavigation(cx: Scope) -> Element { + render! { + GoBackButton { + "Back to the Past" + } + GoForwardButton { + "Back to the Future" /* You see what I did there? 😉 */ + } + } +} +// ANCHOR_END: history_buttons + +fn main() {} diff --git a/docs/router/examples/history_provider.rs b/docs/router/examples/history_provider.rs new file mode 100644 index 000000000..090019ad0 --- /dev/null +++ b/docs/router/examples/history_provider.rs @@ -0,0 +1,29 @@ +#![allow(non_snake_case)] +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +#[derive(Routable, Clone)] +enum Route { + #[route("/")] + Home {}, +} + +// ANCHOR: app +#[inline_props] +fn App(cx: Scope) -> Element { + render! { + Router { + config: || RouterConfig::default().history(WebHistory::default()) + } + } +} +// ANCHOR_END: app + +#[inline_props] +fn Home(cx: Scope) -> Element { + render! { + h1 { "Welcome to the Dioxus Blog!" } + } +} + +fn main() {} diff --git a/docs/router/examples/navigator.rs b/docs/router/examples/navigator.rs new file mode 100644 index 000000000..786f832a4 --- /dev/null +++ b/docs/router/examples/navigator.rs @@ -0,0 +1,56 @@ +#![allow(non_snake_case)] +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +#[derive(Routable, Clone)] +#[rustfmt::skip] +enum Route { + #[route("/")] + Home {}, + #[route("/:...route")] + PageNotFound { route: Vec }, +} + +#[inline_props] +fn App(cx: Scope) -> Element { + render! { + Router {} + } +} + +// ANCHOR: nav +#[inline_props] +fn Home(cx: Scope) -> Element { + let nav = use_navigator(cx); + + // push + nav.push(Route::PageNotFound { route: vec![] }); + + // replace + nav.replace(Route::Home {}); + + // go back + nav.go_back(); + + // go forward + nav.go_forward(); + + render! { + h1 { "Welcome to the Dioxus Blog!" } + } +} +// ANCHOR_END: nav + +#[inline_props] +fn PageNotFound(cx: Scope, route: Vec) -> Element { + render! { + h1 { "Page not found" } + p { "We are terribly sorry, but the page you requested doesn't exist." } + pre { + color: "red", + "log:\nattemped to navigate to: {route:?}" + } + } +} + +fn main() {} diff --git a/docs/router/examples/nest.rs b/docs/router/examples/nest.rs new file mode 100644 index 000000000..f7fe2137c --- /dev/null +++ b/docs/router/examples/nest.rs @@ -0,0 +1,40 @@ +#![allow(non_snake_case, unused)] +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +// ANCHOR: route +#[derive(Routable, Clone)] +// Skipping formatting allows you to indent nests +#[rustfmt::skip] +enum Route { + // Start the /blog nest + #[nest("/blog")] + // You can nest as many times as you want + #[nest("/:id")] + #[route("/post")] + PostId { + // You must include parent dynamic segments in child variants + id: usize, + }, + // End nests manually with #[end_nest] + #[end_nest] + #[route("/:id")] + // The absolute route of BlogPost is /blog/:name + BlogPost { + id: usize, + }, + // Or nests are ended automatically at the end of the enum +} + +#[inline_props] +fn BlogPost(cx: Scope, id: usize) -> Element { + todo!() +} + +#[inline_props] +fn PostId(cx: Scope, id: usize) -> Element { + todo!() +} +// ANCHOR_END: route + +fn main() {} diff --git a/docs/router/examples/outlet.rs b/docs/router/examples/outlet.rs new file mode 100644 index 000000000..b28a61f66 --- /dev/null +++ b/docs/router/examples/outlet.rs @@ -0,0 +1,46 @@ +#![allow(non_snake_case)] +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +// ANCHOR: outlet +#[derive(Routable, Clone)] +#[rustfmt::skip] +enum Route { + #[layout(Wrapper)] + #[route("/")] + Index {}, +} + +#[inline_props] +fn Wrapper(cx: Scope) -> Element { + render! { + header { "header" } + // The index route will be rendered here + Outlet { } + footer { "footer" } + } +} + +#[inline_props] +fn Index(cx: Scope) -> Element { + render! { + h1 { "Index" } + } +} +// ANCHOR_END: outlet + +fn App(cx: Scope) -> Element { + render! { + Router {} + } +} + +fn main() { + let mut vdom = VirtualDom::new(App); + let _ = vdom.rebuild(); + let html = dioxus_ssr::render(&vdom); + assert_eq!( + html, + "
header

Index

footer
" + ); +} diff --git a/docs/router/examples/query_segments.rs b/docs/router/examples/query_segments.rs new file mode 100644 index 000000000..1f3c068ac --- /dev/null +++ b/docs/router/examples/query_segments.rs @@ -0,0 +1,24 @@ +#![allow(non_snake_case, unused)] +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +// ANCHOR: route +#[derive(Routable, Clone)] +#[rustfmt::skip] +enum Route { + // segments that start with ?: are query segments + #[route("/blog?:name")] + BlogPost { + // You must include query segments in child variants + name: String, + }, +} + +// Components must contain the same query segments as their corresponding variant +#[inline_props] +fn BlogPost(cx: Scope, name: String) -> Element { + todo!() +} +// ANCHOR_END: route + +fn main() {} diff --git a/docs/router/examples/router_cfg.rs b/docs/router/examples/router_cfg.rs new file mode 100644 index 000000000..9b29af75c --- /dev/null +++ b/docs/router/examples/router_cfg.rs @@ -0,0 +1,37 @@ +// ANCHOR: router +#![allow(non_snake_case)] +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +/// An enum of all of the possible routes in the app. +#[derive(Routable, Clone)] +enum Route { + // The home page is at the / route + #[route("/")] + // If the name of the component and variant are the same you can omit the component and props name + // #[route("/", ComponentName, PropsName)] + Home {}, +} +// ANCHOR_END: router + +// ANCHOR: app +#[inline_props] +fn App(cx: Scope) -> Element { + render! { + Router { + config: || RouterConfig::default().history(WebHistory::default()) + } + } +} +// ANCHOR_END: app + +// ANCHOR: home +#[inline_props] +fn Home(cx: Scope) -> Element { + render! { + h1 { "Welcome to the Dioxus Blog!" } + } +} +// ANCHOR_END: home + +fn main() {} diff --git a/docs/router/examples/routing_update.rs b/docs/router/examples/routing_update.rs new file mode 100644 index 000000000..d77e34738 --- /dev/null +++ b/docs/router/examples/routing_update.rs @@ -0,0 +1,41 @@ +#![allow(non_snake_case, unused)] +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +// ANCHOR: router +#[derive(Routable, Clone, PartialEq)] +enum Route { + #[route("/")] + Index {}, + #[route("/home")] + Home {}, +} + +#[inline_props] +fn Home(cx: Scope) -> Element { + render! { + p { "Home" } + } +} + +#[inline_props] +fn Index(cx: Scope) -> Element { + render! { + p { "Index" } + } +} + +fn app(cx: Scope) -> Element { + render! { + Router { + config: || RouterConfig::default().on_update(|state|{ + (state.current() == Route::Index {}).then(|| { + NavigationTarget::Internal(Route::Home {}) + }) + }) + } + } +} +// ANCHOR_END: router + +fn main() {} diff --git a/docs/router/examples/static_segments.rs b/docs/router/examples/static_segments.rs new file mode 100644 index 000000000..f38f9c160 --- /dev/null +++ b/docs/router/examples/static_segments.rs @@ -0,0 +1,29 @@ +#![allow(non_snake_case)] +use dioxus::prelude::*; +use dioxus_router::prelude::*; + +// ANCHOR: route +#[derive(Routable, Clone)] +#[rustfmt::skip] +enum Route { + // Routes always start with a slash + #[route("/")] + Home {}, + // You can have multiple segments in a route + #[route("/hello/world")] + HelloWorld {}, +} + +#[inline_props] +fn Home(cx: Scope) -> Element { + todo!() +} + +#[inline_props] +fn HelloWorld(cx: Scope) -> Element { + todo!() +} +// ANCHOR_END: route + + +fn main() {} \ No newline at end of file diff --git a/docs/router/src/SUMMARY.md b/docs/router/src/SUMMARY.md index 40788bbb1..19ffb2ca7 100644 --- a/docs/router/src/SUMMARY.md +++ b/docs/router/src/SUMMARY.md @@ -10,3 +10,16 @@ - [Navigation Targets](./example/navigation-targets.md) - [Redirection Perfection](./example/redirection-perfection.md) - [Full Code](./example/full-code.md) + +# Reference + +- [Adding the Router to Your Application](./reference/index.md) +- [Defining Routes](./reference/routes/index.md) + - [Nested Routes](./reference/routes/nested.md) +- [Layouts](./reference/layouts.md) +- [Navigation](./reference/navigation/index.md) + - [Programmatic Navigation](./reference/navigation/programmatic.md) +- [History Providers](./reference/history-providers.md) +- [History Buttons](./reference/history-buttons.md) +- [Static Generation](./reference/static-generation.md) +- [Routing Update Callback](./reference/routing-update-callback.md) diff --git a/docs/router/src/example/first-route.md b/docs/router/src/example/first-route.md index 9bedcbf11..5135f6a8f 100644 --- a/docs/router/src/example/first-route.md +++ b/docs/router/src/example/first-route.md @@ -13,7 +13,7 @@ First, we need an actual page to route to! Let's add a homepage component: {{#include ../../examples/first_route.rs:home}} ``` -## To Route or Not to Route +## Creating Routes We want to use Dioxus Router to separate our application into different "pages". Dioxus Router will then determine which page to render based on the URL path. @@ -35,7 +35,7 @@ you enter a different path for the URL, nothing should be displayed. This is because we told Dioxus Router to render the `Home` component only when the URL path is `/`. -## What if a Route Doesn't Exist? +## Fallback Route In our example, when a route doesn't exist Dioxus Router doesn't render anything. Many sites also have a "404" page when a path does not exist. Let's add one to our site. diff --git a/docs/router/src/example/navigation-targets.md b/docs/router/src/example/navigation-targets.md index 0ca7fcef5..fb12f913f 100644 --- a/docs/router/src/example/navigation-targets.md +++ b/docs/router/src/example/navigation-targets.md @@ -22,6 +22,6 @@ If we need a link to an external page we can do it like this: {{#include ../../examples/external_link.rs:component}} ``` -[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External -[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal -[`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html +[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.External +[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.Internal +[`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html diff --git a/docs/router/src/features/failures/external.md b/docs/router/src/features/failures/external.md deleted file mode 100644 index 86c08d83e..000000000 --- a/docs/router/src/features/failures/external.md +++ /dev/null @@ -1 +0,0 @@ -# External Navigation Failure diff --git a/docs/router/src/features/failures/index.md b/docs/router/src/features/failures/index.md deleted file mode 100644 index 48b44c15d..000000000 --- a/docs/router/src/features/failures/index.md +++ /dev/null @@ -1 +0,0 @@ -# Navigation Failures diff --git a/docs/router/src/features/failures/named.md b/docs/router/src/features/failures/named.md deleted file mode 100644 index ff95a96b0..000000000 --- a/docs/router/src/features/failures/named.md +++ /dev/null @@ -1 +0,0 @@ -# Named Navigation Failure diff --git a/docs/router/src/features/failures/redirection-limit.md b/docs/router/src/features/failures/redirection-limit.md deleted file mode 100644 index cc9dfef5c..000000000 --- a/docs/router/src/features/failures/redirection-limit.md +++ /dev/null @@ -1 +0,0 @@ -# Redirection Limit Failure diff --git a/docs/router/src/features/history-buttons.md b/docs/router/src/features/history-buttons.md deleted file mode 100644 index 2959db069..000000000 --- a/docs/router/src/features/history-buttons.md +++ /dev/null @@ -1 +0,0 @@ -# History Buttons diff --git a/docs/router/src/features/history-providers.md b/docs/router/src/features/history-providers.md deleted file mode 100644 index e12b846d8..000000000 --- a/docs/router/src/features/history-providers.md +++ /dev/null @@ -1 +0,0 @@ -# History Providers diff --git a/docs/router/src/features/index.md b/docs/router/src/features/index.md deleted file mode 100644 index 5c3609ac7..000000000 --- a/docs/router/src/features/index.md +++ /dev/null @@ -1 +0,0 @@ -# Adding the Router to Your Application diff --git a/docs/router/src/features/navigation/external.md b/docs/router/src/features/navigation/external.md deleted file mode 100644 index 5a1a21198..000000000 --- a/docs/router/src/features/navigation/external.md +++ /dev/null @@ -1 +0,0 @@ -# External Navigation diff --git a/docs/router/src/features/navigation/index.md b/docs/router/src/features/navigation/index.md deleted file mode 100644 index fa31ecbc7..000000000 --- a/docs/router/src/features/navigation/index.md +++ /dev/null @@ -1 +0,0 @@ -# Links & Navigation diff --git a/docs/router/src/features/navigation/name.md b/docs/router/src/features/navigation/name.md deleted file mode 100644 index 7eee17d55..000000000 --- a/docs/router/src/features/navigation/name.md +++ /dev/null @@ -1 +0,0 @@ -# Named Navigation diff --git a/docs/router/src/features/navigation/programmatic.md b/docs/router/src/features/navigation/programmatic.md deleted file mode 100644 index 5c203a5ee..000000000 --- a/docs/router/src/features/navigation/programmatic.md +++ /dev/null @@ -1 +0,0 @@ -# Programmatic Navigation diff --git a/docs/router/src/features/outlets.md b/docs/router/src/features/outlets.md deleted file mode 100644 index efe3a78b9..000000000 --- a/docs/router/src/features/outlets.md +++ /dev/null @@ -1 +0,0 @@ -# Outlets diff --git a/docs/router/src/features/query.md b/docs/router/src/features/query.md deleted file mode 100644 index f0c52077d..000000000 --- a/docs/router/src/features/query.md +++ /dev/null @@ -1 +0,0 @@ -# Query diff --git a/docs/router/src/features/routes/catch_all.md b/docs/router/src/features/routes/catch_all.md deleted file mode 100644 index 663b76d09..000000000 --- a/docs/router/src/features/routes/catch_all.md +++ /dev/null @@ -1 +0,0 @@ -# Catch All Routes diff --git a/docs/router/src/features/routes/fallback.md b/docs/router/src/features/routes/fallback.md deleted file mode 100644 index 2f0fcdf41..000000000 --- a/docs/router/src/features/routes/fallback.md +++ /dev/null @@ -1 +0,0 @@ -# Fallback Routes (404 page) diff --git a/docs/router/src/features/routes/index.md b/docs/router/src/features/routes/index.md deleted file mode 100644 index ea7059eed..000000000 --- a/docs/router/src/features/routes/index.md +++ /dev/null @@ -1 +0,0 @@ -# Defining Routes diff --git a/docs/router/src/features/routes/matching.md b/docs/router/src/features/routes/matching.md deleted file mode 100644 index 9a1ed8c45..000000000 --- a/docs/router/src/features/routes/matching.md +++ /dev/null @@ -1 +0,0 @@ -# Matching Routes diff --git a/docs/router/src/features/routes/multiple-and-redirect.md b/docs/router/src/features/routes/multiple-and-redirect.md deleted file mode 100644 index 8d15cc35f..000000000 --- a/docs/router/src/features/routes/multiple-and-redirect.md +++ /dev/null @@ -1 +0,0 @@ -# Multiple Components & Redirects diff --git a/docs/router/src/features/routes/nested.md b/docs/router/src/features/routes/nested.md deleted file mode 100644 index 1706df912..000000000 --- a/docs/router/src/features/routes/nested.md +++ /dev/null @@ -1 +0,0 @@ -# Nested Routes diff --git a/docs/router/src/features/routing-update-callback.md b/docs/router/src/features/routing-update-callback.md deleted file mode 100644 index 37ec1d977..000000000 --- a/docs/router/src/features/routing-update-callback.md +++ /dev/null @@ -1 +0,0 @@ -# Routing Update Callback diff --git a/docs/router/src/features/sitemap-generation.md b/docs/router/src/features/sitemap-generation.md deleted file mode 100644 index 61ad6b984..000000000 --- a/docs/router/src/features/sitemap-generation.md +++ /dev/null @@ -1 +0,0 @@ -# Sitemap Generation diff --git a/docs/router/src/index.md b/docs/router/src/index.md index ca39cf6c4..56a7d5deb 100644 --- a/docs/router/src/index.md +++ b/docs/router/src/index.md @@ -15,12 +15,12 @@ cargo add dioxus-router This book is intended to get you up to speed with Dioxus Router. It is split into two sections: -1. The [Reference](./reference/index.md) part explains individual features in - depth. You can read it start to finish, or you can read individual chapters +1. The [reference](./reference/index.md) section explains individual features in + depth. You can read it from start to finish, or you can read individual chapters in whatever order you want. 2. If you prefer a learning-by-doing approach, you can check out the _[example project](./example/index.md)_. It guides you through - creating a dioxus app, setting up the router and using some of its + creating a dioxus app, setting up the router, and using some of its functionality. > Please note that this is not the only documentation for the Dioxus Router. You diff --git a/docs/router/src/reference/failures/index.md b/docs/router/src/reference/failures/index.md deleted file mode 100644 index ae053d356..000000000 --- a/docs/router/src/reference/failures/index.md +++ /dev/null @@ -1,78 +0,0 @@ -# Navigation Failures - -Some specific operations can cause a failure within router operations. The -subchapters contain information on how the router lets us handle such failures. - -# External Navigation Failure - -> This section doesn't apply when specifying a `target` on a [`Link`]. See the -> chapter about [external navigation](../navigation/external.md) for more -> details. - -When we ask the router to navigate to an external target, either through -[programmatic navigation](../navigation/programmatic.md) or a -[redirect](../routes/multiple-and-redirect.md#redirects) the router needs to -navigate to an external target without being able to rely on an anchor element. - -This will only work in the browser, when using either [`WebHistory`] or -[`WebHashHistory`]. - -## Failure handling - -When the router encounters an external navigation it cannot fulfill, it changes -the path to `/` and shows some fallback content. - -> You can detect if the router is in the external navigation failure handling -> state by [checking](../navigation/name.md#check-if-a-name-is-present) if the -> [`FailureExternalNavigation`] name is present. - -The default fallback explains to the user that the navigation was unsuccessful -and provides them with a [`Link`] to fulfill it manually. It also allows them to -go back to the previous page. - -You can override it by setting the `failure_external_navigation` value of the -[`RouterConfiguration`]. The external URL will be provided via the -[`FailureExternalNavigation`] parameter. - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -# use dioxus::prelude::*; -# extern crate dioxus_router; -# use dioxus_router::prelude::*; -fn ExternalNavigationFallback(cx: Scope) -> Element { - let route = use_route(cx).expect("is nested within a Router component"); - let url = route - .parameter::() - .unwrap_or_default(); - - render! { - h1 { "External navigation failure!" } - Link { - target: url, - "Go to external site" - } - } -} - -fn App(cx: Scope) -> Element { - use_router( - cx, - &|| RouterConfiguration { - failure_external_navigation: comp(ExternalNavigationFallback), - ..Default::default() - }, - &|| Segment::empty() - ); - - render! { - Outlet { } - } -} -``` - -[`FailureExternalNavigation`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.FailureExternalNavigation.html -[`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html -[`RouterConfiguration`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/struct.RouterConfiguration.html -[`WebHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html -[`WebHashHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHashHistory.html diff --git a/docs/router/src/reference/history-buttons.md b/docs/router/src/reference/history-buttons.md index f47c99c6f..ad3ffbe22 100644 --- a/docs/router/src/reference/history-buttons.md +++ b/docs/router/src/reference/history-buttons.md @@ -1,11 +1,11 @@ # History Buttons Some platforms, like web browsers, provide users with an easy way to navigate -through an apps history. They have UI elements or integrate with the OS. +through an app's history. They have UI elements or integrate with the OS. However, native platforms usually don't provide such amenities, which means that apps wanting users to have access to them, need to implement them. For this -reason the router comes with two components, which emulate a browsers back and +reason, the router comes with two components, which emulate a browser's back and forward buttons: - [`GoBackButton`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoBackButton.html) @@ -14,38 +14,19 @@ forward buttons: > If you want to navigate through the history programmatically, take a look at > [`programmatic navigation`](./navigation/programmatic.md). -```rust, no_run, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -use dioxus::prelude::*; -# extern crate dioxus_router; -use dioxus_router::prelude::*; - -fn HistoryNavigation(cx: Scope) -> Element { - render! { - GoBackButton { - "Back to the Past" - } - GoForwardButton { - "Back to the Future" /* You see what I did there? 😉 */ - } - } -} +```rust, no_run +{{#include ../../examples/history_buttons.rs:history_buttons}} ``` As you might know, browsers usually disable the back and forward buttons if -there is no history to navigate to. The routers history buttons try to do that +there is no history to navigate to. The router's history buttons try to do that too, but depending on the [history provider] that might not be possible. -Importantly, neither [`WebHistory`] nor [`WebHashHistory`] support that feature. +Importantly, neither [`WebHistory`] supports that feature. This is due to limitations of the browser History API. -However, in both cases the router will just ignore button presses, if there is +However, in both cases, the router will just ignore button presses, if there is no history to navigate to. -Also, when using [`WebHistory`] or [`WebHashHistory`], the history buttons might +Also, when using [`WebHistory`], the history buttons might navigate a user to a history entry outside your app. - -[history provider]: ./history-providers.md -[`WebHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html -[`WebHashHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHashHistory.html diff --git a/docs/router/src/reference/history-providers.md b/docs/router/src/reference/history-providers.md index ec2a5660a..b2a12bfd8 100644 --- a/docs/router/src/reference/history-providers.md +++ b/docs/router/src/reference/history-providers.md @@ -1,43 +1,20 @@ # History Providers -In order to provide the ability to traverse the navigation history, the router -uses [`HistoryProvider`]s. Those implement the actual back-and-forth -functionality. +[`HistoryProvider`]s are used by the router to keep track of the navigation history +and update any external state (e.g. the browser's URL). The router provides five [`HistoryProvider`]s, but you can also create your own. The five default implementations are: - The [`MemoryHistory`] is a custom implementation that works in memory. -- The [`WebHistory`] integrates with the browsers URL. +- The [`WebHistory`] integrates with the browser's URL. -By default the router uses the [`MemoryHistory`]. It might be changed to use +By default, the router uses the [`MemoryHistory`]. It might be changed to use [`WebHistory`] when the `web` feature is active, but that is not guaranteed. You can override the default history: ```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -use dioxus::prelude::*; -# extern crate dioxus_router; -use dioxus_router::{prelude::*, history::WebHashHistory}; - -fn App(cx: Scope) -> Element { - use_router( - cx, - &|| RouterConfiguration { - history: Box::new(WebHashHistory::new(true)), - ..Default::default() - }, - &|| Segment::empty() - ); - - render! { - Outlet { } - } -} +{{#include ../../examples/history_provider.rs:app}} ``` -[`HistoryProvider`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/trait.HistoryProvider.html -[`MemoryHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.MemoryHistory.html -[`WebHistory`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html diff --git a/docs/router/src/reference/index.md b/docs/router/src/reference/index.md index 20845d6b2..add681671 100644 --- a/docs/router/src/reference/index.md +++ b/docs/router/src/reference/index.md @@ -1,54 +1,23 @@ # Adding the Router to Your Application -In this chapter we will learn how to add the router to our app. By it self, this +In this chapter, we will learn how to add the router to our app. By itself, this is not very useful. However, it is a prerequisite for all the functionality described in the other chapters. > Make sure you added the `dioxus-router` dependency as explained in the > [introduction](../index.md). -In most cases we want to add the router to the root component of our app. This -way, we can ensure that we have access to all its functionality everywhere. We -add it by using the [`use_router`] hook +In most cases, we want to add the router to the root component of our app. This +way, we can ensure that we have access to all its functionality everywhere. + +First, we define the router with the router macro: ```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -use dioxus::prelude::*; -# extern crate dioxus_router; -use dioxus_router::prelude::*; -# extern crate dioxus_ssr; - -// This is the component we pass to dioxus when launching our app. -fn App(cx: Scope) -> Element { - // Here we add the router. All components inside `App` have access to its - // functionality. - let routes = use_router( - cx, - // The router can be configured with this parameter. - &|| RouterConfiguration { - # synchronous: true, - ..Default::default() - }, - // This tells the router about all the routes in our application. As we - // don't have any, we pass an empty segment - &|| Segment::empty() - ); - - render! { - h1 { "Our sites title" } - - // The Outlet tells the Router where to render active content. - Outlet { } - } -} -# -# let mut vdom = VirtualDom::new(App); -# let _ = vdom.rebuild(); -# assert_eq!( -# dioxus_ssr::render(&vdom), -# "

Our sites title

" -# ); +{{#include ../../examples/first_route.rs:router}} ``` -[`use_router`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html +Then we render the router with the [`Router`] component. + +```rust, no_run +{{#include ../../examples/first_route.rs:app}} +``` diff --git a/docs/router/src/reference/layouts.md b/docs/router/src/reference/layouts.md new file mode 100644 index 000000000..365ad060b --- /dev/null +++ b/docs/router/src/reference/layouts.md @@ -0,0 +1,19 @@ +# Layouts + +Layouts allow you to + +[`Outlet`]s tell the router where to render content in layouts. In the following example, +the Index will be rendered within the [`Outlet`]. + +```rust, no_run +{{#include ../../examples/outlet.rs:outlet}} +``` + +The example above will output the following HTML (line breaks added for +readability): + +```html +
header
+

Index

+
footer
+``` diff --git a/docs/router/src/reference/navigation/external.md b/docs/router/src/reference/navigation/external.md deleted file mode 100644 index 09b5b4f84..000000000 --- a/docs/router/src/reference/navigation/external.md +++ /dev/null @@ -1,71 +0,0 @@ -# External Navigation - -In modern apps, and especially on the web, we often want to send our users to an -other website. [`External`] allows us to make a [`Link`] navigate to an -external page. - -> You might already now about -> [external navigation failures](../failures/external.md). The [`Link`] -> component doesn't rely on the code path where those originate. Therefore a -> [`Link`] will never trigger an external navigation failure. - -Strictly speaking, a [`Link`] is not necessary for navigating to external -targets, since by definition the router cannot handle them internally. However, -the [`Link`] component is more convenient to use, as it automatically sets the -`rel` attribute for the link, when the target is external. - -## Code Example - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -use dioxus::prelude::*; -# extern crate dioxus_router; -use dioxus_router::prelude::*; -# extern crate dioxus_ssr; - -fn App(cx: Scope) -> Element { - use_router( - cx, - &|| RouterConfiguration { - # synchronous: true, - ..Default::default() - }, - &|| Segment::empty() - ); - - render! { - // links need to be inside a router, even if they navigate to an - // external page - Link { - target: NavigationTarget::External("https://dioxuslabs.com/".into()), - "Go to the dioxus home page" - } - Link { - target: "https://dioxuslabs.com/", // short form - "Go to the dioxus home page 2" - } - } -} -# -# let mut vdom = VirtualDom::new(App); -# vdom.rebuild(); -# let html = dioxus_ssr::render(&vdom); -# assert_eq!( -# html, -# format!( -# "{text}{text} 2", -# 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 diff --git a/docs/router/src/reference/navigation/index.md b/docs/router/src/reference/navigation/index.md index 577af253f..2abf8477e 100644 --- a/docs/router/src/reference/navigation/index.md +++ b/docs/router/src/reference/navigation/index.md @@ -1,59 +1,39 @@ # Links & Navigation When we split our app into pages, we need to provide our users with a way to -navigate between them. On regular web pages we'd use an anchor element for that, +navigate between them. On regular web pages, we'd use an anchor element for that, like this: ```html Link to an other page ``` -However, we cannot do that when using the router for two reasons: +However, we cannot do that when using the router for three reasons: 1. Anchor tags make the browser load a new page from the server. This takes a lot of time, and it is much faster to let the router handle the navigation client-side. 2. Navigation using anchor tags only works when the app is running inside a browser. This means we cannot use them inside apps using Dioxus Desktop. +3. Anchor tags cannot check if the target page exists. This means we cannot + prevent accidentally linking to non-existent pages. To solve these problems, the router provides us with a [`Link`] component we can use like this: ```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -# use dioxus::prelude::*; -# extern crate dioxus_router; -# use dioxus_router::prelude::*; -fn SomeComponent(cx: Scope) -> Element { - render! { - Link { - target: NavigationTarget::Internal(String::from("/some/path")), - "Link text" - } - Link { - target: "/some/path", // short form - "Other link text" - } - } -} +{{#include ../../../examples/links.rs:nav}} ``` The `target` in the example above is similar to the `href` of a regular anchor element. However, it tells the router more about what kind of navigation it -should perform: +should perform. It accepts something that can be converted into a +[`NavigationTarget`]: -- The example uses [`Internal`]. We give it an arbitrary path that will be - merged with the current URL. -- [`Named`] allows us to navigate within our app using predefined names. - See the chapter about [named navigation](./name.md) for more details. -- [`External`] allows us to navigate to URLs outside of our app. See the - chapter about [external navigation](./external.md) for more details. +- The example uses a Internal route. This is the most common type of navigation. + It tells the router to navigate to a page within our app by passing a variant of a [`Routable`] enum. This type of navigation can never fail if the link component is used inside a router component. +- [`External`] allows us to navigate to URLs outside of our app. This is useful + for links to external websites. NavigationTarget::External accepts an URL to navigate to. This type of navigation can fail if the URL is invalid. > The [`Link`] accepts several props that modify its behavior. See the API docs > for more details. - -[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External -[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal -[`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html -[`Named`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Named diff --git a/docs/router/src/reference/navigation/name.md b/docs/router/src/reference/navigation/name.md deleted file mode 100644 index b5ca99fb2..000000000 --- a/docs/router/src/reference/navigation/name.md +++ /dev/null @@ -1,120 +0,0 @@ -# Named Navigation - -When creating large applications, it can become difficult to keep track of all -routes and how to navigate to them. It also can be hard to find all links to -them, which makes it difficult to change paths. - -To solve these problems, the router implements named navigation. When we define -our routes we can give them arbitrary, unique names (completely independent from -the path) and later ask the router to navigate to those names. The router will -automatically create the actual path to navigate to, even inserting required -parameters. - -_Named_ navigation has a few advantages over _path-based_ navigation: - -- Links can be created without knowing the actual path. -- It is much easier to find all links to a specific route. -- The router knows what links are invalid (and will panic in debug builds). - -> When the router encounters an invalid link in a release build, it has to -> handle that problem. You can hook into that process, to display a custom error -> message. See the chapter about -> [named navigation failures](../failures/named.md). - -> The router will automatically define the name [`RootIndex`] to refer to the -> root index route (`/`). -> -> It will also add other names (all of them are in the prelude module) in -> certain conditions. None of these names can be used for app defined routes. - -## Code Example - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -use dioxus::prelude::*; -# extern crate dioxus_router; -use dioxus_router::prelude::*; -# extern crate dioxus_ssr; - -// we define a unit struct which will serve as our name -struct TargetName; - -fn Source(cx: Scope) -> Element { - render! { - Link { - // instead of InternalTarget we use NamedTarget (via the `named` fn) - // we can use the returned value to add parameters or a query - target: named::().query("query"), - "Go to target" - } - } -} - -fn Target(cx: Scope) -> Element { - render! { - h1 { "Target" } - } -} - -fn App(cx: Scope) -> Element { - use_router( - cx, - &|| RouterConfiguration { - # synchronous: true, - ..Default::default() - }, - &|| { - Segment::content(comp(Source)) - .fixed( - "target_path", - Route::content(comp(Target)).name::() - ) - } - ); - - render! { - Outlet { } - } -} -# -# let mut vdom = VirtualDom::new(App); -# vdom.rebuild(); -# let html = dioxus_ssr::render(&vdom); -# assert_eq!( -# html, -# format!( -# "Go to target", -# 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::(), false) { - // do something - } - - // ... - # todo!() -} -``` - -[`RootIndex`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.RootIndex.html diff --git a/docs/router/src/reference/navigation/programmatic.md b/docs/router/src/reference/navigation/programmatic.md index ae770c0e0..894dd968e 100644 --- a/docs/router/src/reference/navigation/programmatic.md +++ b/docs/router/src/reference/navigation/programmatic.md @@ -3,68 +3,25 @@ Sometimes we want our application to navigate to another page without having the user click on a link. This is called programmatic navigation. -## Acquiring a [`Navigator`] +## Using a Navigator -To use programmatic navigation, we first have to acquire a [`Navigator`]. For -that purpose we can use the [`use_navigate`] hook. - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -use dioxus::prelude::*; -# extern crate dioxus_router; -use dioxus_router::prelude::*; - -fn Content(cx: Scope) -> Element { - let nav = use_navigate(cx).expect("called inside a router"); - - // ... - # unimplemented!() -} -``` - -## Triggering a Navigation +We can get a navigator with the [`use_navigator`] hook. This hook returns a [`Navigator`]. We can use the [`Navigator`] to trigger four different kinds of navigation: - `push` will navigate to the target. It works like a regular anchor tag. - `replace` works like `push`, except that it replaces the current history entry - instead of adding a new one. This means the prior page cannot be restored with - the browsers back button. -- `Go back` works like the browsers back button. -- `Go forward` works like the browsers forward button (the opposite of the back - button). + instead of adding a new one. This means the prior page cannot be restored with the browser's back button. +- `Go back` works like the browser's back button. +- `Go forward` works like the browser's forward button. ```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -# use dioxus::prelude::*; -# extern crate dioxus_router; -# use dioxus_router::prelude::*; -# -fn Content(cx: Scope) -> Element { - let nav = use_navigate(cx).expect("called inside a router"); - - // push - nav.push("/target"); - - // replace - nav.replace("/target"); - - // go back - nav.go_back(); - - // go forward - nav.go_forward(); - - // ... - # unimplemented!() -} +{{#include ../../../examples/navigator.rs:nav}} ``` You might have noticed that, like [`Link`], the [`Navigator`]s `push` and -`replace` functions take a [`NavigationTarget`]. This means we can use -[`Internal`], [`Named`] and [`External`]. +`replace` functions take a [`NavigationTarget`]. This means we can use either +[`Internal`], or [`External`] targets. ## External Navigation Targets @@ -72,13 +29,4 @@ Unlike a [`Link`], the [`Navigator`] cannot rely on the browser (or webview) to handle navigation to external targets via a generated anchor element. This means, that under certain conditions, navigation to external targets can -fail. See the chapter about -[external navigation failures](../failures/external.md) for more details. - -[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External -[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal -[`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html -[`Named`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Named -[`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html -[`Navigator`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/hooks/struct.Navigator.html -[`use_navigate`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_navigate.html +fail. diff --git a/docs/router/src/reference/outlets.md b/docs/router/src/reference/outlets.md deleted file mode 100644 index 3f9926e73..000000000 --- a/docs/router/src/reference/outlets.md +++ /dev/null @@ -1,242 +0,0 @@ -# Outlets - -[`Outlet`]s tell the router where to render content. In the following example -the active routes content will be rendered within the [`Outlet`]. - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -# use dioxus::prelude::*; -# extern crate dioxus_router; -# use dioxus_router::prelude::*; -# extern crate dioxus_ssr; - -fn Index(cx: Scope) -> Element { - render! { - h1 { "Index" } - } -} - -fn App(cx: Scope) -> Element { - use_router( - cx, - &|| RouterConfiguration { - # synchronous: true, - ..Default::default() - }, - &|| Segment::content(comp(Index)) - ); - - render! { - header { "header" } - Outlet { } - footer { "footer" } - } -} -# -# let mut vdom = VirtualDom::new(App); -# vdom.rebuild(); -# let html = dioxus_ssr::render(&vdom); -# assert_eq!( -# html, -# "
header

Index

footer
" -# ); -``` - -The example above will output the following HTML (line breaks added for -readability): - -```html -
header
-

Index

-
footer
-``` - -## Nested Outlets - -When using nested routes, we need to provide equally nested [`Outlet`]s. - -> Learn more about [nested routes](./routes/nested.md) in their own chapter. - -## Named Outlets - -When building complex apps, we often need to display multiple pieces of content -simultaneously. For example, we might have a sidebar that changes its content in -sync with the main part of the page. - -When defining our routes, we can use `RouteContentMulti` instead of -`RouteContent::Component` (we've been using this through the `Into` trait) to -tell the router about our content. - -We then can use a named [`Outlet`] in our output, to tell the router where to -put the side content. - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -# use dioxus::prelude::*; -# extern crate dioxus_router; -# use dioxus_router::prelude::*; -# extern crate dioxus_ssr; -# - -fn Main(cx: Scope) -> Element { - render! { - main { "Main Content" } - } -} - -struct AsideName; -fn Aside(cx: Scope) -> Element { - render! { - aside { "Side Content" } - } -} - -fn App(cx: Scope) -> Element { - use_router( - cx, - &|| RouterConfiguration { - # synchronous: true, - ..Default::default() - }, - &|| { - Segment::content( - multi(Some(comp(Main))) - .add_named::(comp(Aside)) - ) - } - ); - - render! { - Outlet { } - Outlet { - name: Name::of::() - } - } -} -# -# let mut vdom = VirtualDom::new(App); -# vdom.rebuild(); -# let html = dioxus_ssr::render(&vdom); -# assert_eq!(html, "
Main Content
"); -``` - -The example above will output the following HTML (line breaks added for -readability): - -```html -
Main Content
- -``` - -## Outlet depth override - -When nesting [`Outlet`]s, they communicate with each other. This allows the -nested [`Outlet`] to render the content of the nested route. - -We can override the detected value. Be careful when doing so, it is incredibly -easy to create an unterminated recursion. See below for an example of that. - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -# use dioxus::prelude::*; -# extern crate dioxus_router; -# use dioxus_router::{history::MemoryHistory, prelude::*}; -# extern crate dioxus_ssr; -# -fn RootContent(cx: Scope) -> Element { - render! { - h1 { "Root" } - Outlet { } - } -} - -fn NestedContent(cx: Scope) -> Element { - render! { - h2 { "Nested" } - } -} - -fn App(cx: Scope) -> Element { - use_router( - cx, - &|| RouterConfiguration { - # synchronous: true, - # history: Box::new(MemoryHistory::with_initial_path("/root").unwrap()), - ..Default::default() - }, - &|| { - Segment::empty().fixed( - "root", - Route::content(comp(RootContent)).nested( - Segment::content(comp(NestedContent)) - ) - ) - } - ); - - render! { - Outlet { - depth: 1 - } - } -} -# -# let mut vdom = VirtualDom::new(App); -# vdom.rebuild(); -# let html = dioxus_ssr::render(&vdom); -# assert_eq!(html, "

Nested

"); -``` - -The example above will output the following HTML (line breaks added for -readability): - -```html -

Nested

-``` - -### Outlet recursion - -This code will create a crash due to an unterminated recursion using -[`Outlet`]s. - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -# use dioxus::prelude::*; -# extern crate dioxus_router; -# use dioxus_router::prelude::*; -# -fn Content(cx: Scope) -> Element { - render! { - h1 { "Heyho!" } - Outlet { - depth: 0, - } - } -} - -fn App(cx: Scope) -> Element { - use_router(cx, &Default::default, &|| Segment::content(comp(Content))); - - render! { - Outlet { } - } -} -``` - -The [`Outlet`] in the `App` component has no parent [`Outlet`], so its depth -will be `0`. When rendering for the path `/`, it therefore will render the -`Content` component. - -The `Content` component will render an `h1` and an [`Outlet`]. That [`OUtlet`] -would usually have a depth of `1`, since it is a descendant of the [`Outlet`] in -the `App` component. However, we override its depth to `0`, so it will render -the `Content` component. - -That means the `Content` component will recurse until someone (e.g. the OS) puts -a stop to it. - -[`Outlet`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html diff --git a/docs/router/src/reference/query.md b/docs/router/src/reference/query.md deleted file mode 100644 index 9cbbccd42..000000000 --- a/docs/router/src/reference/query.md +++ /dev/null @@ -1,89 +0,0 @@ -# Query - -Some apps use the query part of the URL to encode information. The router allows -you to easily access the query, as well as set it when navigating. - -## Accessing the query - -The [`use_route`] hook allows us to access the current query in two ways. The -returned `struct` contains a `query` field, that contains the query (without the -leading `?`). - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -use dioxus::prelude::*; -# extern crate dioxus_router; -use dioxus_router::prelude::*; - -fn SomeComponent(cx: Scope) -> Element { - let route = use_route(cx).expect("nested in Router"); - - let query = route.query.clone().unwrap(); - - // ... - # unimplemented!() -} -``` - -## Setting the query - -When navigating we can tell the router to change the query. However, the method -we use to do this is very different, depending on how we specify our target. - -### [`Internal`] and [`External`] - -When using [`Internal`] or [`External`] we have to append our query manually. - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -# use dioxus::prelude::*; -# extern crate dioxus_router; -# use dioxus_router::prelude::*; -# -fn SomeComponent(cx: Scope) -> Element { - render! { - Link { - target: NavigationTarget::Internal("/some/path?query=yes".into()), - "Internal target" - } - Link { - target: NavigationTarget::External("https://dioxuslab.com?query=yes".into()), - "External target" - } - } -} -``` - -### [`Named`] - -When using [named navigation](./navigation/name.md) we can pass the query via -a function. - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -# use dioxus::prelude::*; -# extern crate dioxus_router; -# use dioxus_router::prelude::*; -# struct Target; -# -fn SomeComponent(cx: Scope) -> Element { - render! { - Link { - target: named::().query("query=yes"), - "Query String" - } - Link { - target: named::().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 diff --git a/docs/router/src/reference/redirects.md b/docs/router/src/reference/redirects.md new file mode 100644 index 000000000..70ea4d7cd --- /dev/null +++ b/docs/router/src/reference/redirects.md @@ -0,0 +1,13 @@ +# Redirects + +In some cases, we may want to redirect our users to another page whenever they +open a specific path. We can tell the router to do this with the `#[redirect]` +attribute. + +The `#[redirect]` attribute accepts a route and a closure with all of the parameters defined in the route. The closure must return a [`NavigationTarget`]. + +In the following example, we will redirect everybody from `/myblog` and `/myblog/:id` to `/blog` and `/blog/:id` respectively + +```rust, no_run +{{#include ../../examples/full_example.rs:router}} +``` diff --git a/docs/router/src/reference/routes/catch_all.md b/docs/router/src/reference/routes/catch_all.md deleted file mode 100644 index f411de58a..000000000 --- a/docs/router/src/reference/routes/catch_all.md +++ /dev/null @@ -1,166 +0,0 @@ -# Catch All Routes - -Many modern web apps store parameters within their current path. This allows -users to share URLs that link to a specific bit of content. We can create this -functionality with catch all routes. - -> If you want to change what route is active based on the format of the -> parameter, see [Matching Routes](./matching.md). - -> The parameter will be URL decoded. - -## Creating a content component - -We start by creating a component that uses the parameters value. - -We can get the current state of the router using the [`use_route`] hook. From -that state we can extract the current value of our parameter by using a key we -will later also define on our route. - -> It is **VERY IMPORTANT** to drop the object returned by the [`use_route`] -> hook once our component finished rendering. Otherwise the entire router will -> be frozen. - -> The [`use_route`] hook can only be used in components nested within a -> component that called [`use_router`]. - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -use dioxus::prelude::*; -# extern crate dioxus_router; -use dioxus_router::prelude::*; - -struct Name; - -fn Greeting(cx: Scope) -> Element { - let route = use_route(cx).expect("is nested within a Router component"); - let name = route.parameter::() - .map(|name| name.clone()) - .unwrap_or(String::from("world")); - - render! { - p { "Hello, {name}!" } - } -} -``` - -## Defining the routes - -Now we can define our route. Unlike a fixed [`Route`], a [`ParameterRoute`] -needs two arguments to be created. - -> Also note that each [`Segment`] can have exactly one parameter or -> [fallback route](./fallback.md). -> -> For that reason, the example below would not work in practice, but showing -> both forms (explicit and short) is more important for this example. - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -# use dioxus::prelude::*; -# extern crate dioxus_router; -# use dioxus_router::prelude::*; -# fn Greeting(cx: Scope) -> Element { unimplemented!() } -# -struct Name; - -fn App(cx: Scope) -> Element { - use_router( - cx, - &|| RouterConfiguration { - ..Default::default() - }, - &|| { - Segment::empty() - .catch_all(ParameterRoute::content::(comp(Greeting))) - .catch_all((comp(Greeting), Name { })) // same in short - } - ); - - // ... - # unimplemented!() -} -``` - -## Interaction with other routes - -Each individual [`Segment`] can only ever have one active route. This means that -when a [`Segment`] has more than just a catch all route, the router has to -decide which is active. It does that this way: - -0. If the segment is not specified (i.e. `/`), then the index route will be - active. -1. If a [_fixed_](./index.md#fixed-routes) route matches the current path, it - will be active. -2. If a [_matching_ route](./matching.md) matches the current path, it will be - active. _Matching_ routes are checked in the order they are defined. -3. If neither a _fixed_ nor a _matching_ route is active, the _catch all_ route - or [_fallback_ route](./fallback.md) will be. - -Step 0 means that if we want a parameter to be empty, that needs to be specified -by the path, i.e. `//`. - -> Be careful with using catch all routes on the root [`Segment`]. Navigating to -> paths starting with `//` will **NOT** work. This is not a limitation of the -> router, but rather of how relative URLs work. -> -> If you absolutely need an empty parameter on the root [`Segment`], a URL like -> this _could_ work: -> -> - `https://your-site.example//` for web sites -> - `dioxus://index.html//` for desktop apps - -## Full Code - -```rust, no_run -# // Hidden lines (like this one) make the documentation tests work. -# extern crate dioxus; -# use dioxus::prelude::*; -# extern crate dioxus_router; -# use dioxus_router::{history::MemoryHistory, prelude::*}; -# extern crate dioxus_ssr; -# -struct Name; - -fn Greeting(cx: Scope) -> Element { - let route = use_route(cx).expect("is nested within a Router component"); - let name = route.parameter::() - .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::*;