diff --git a/packages/yew-router/src/switch.rs b/packages/yew-router/src/switch.rs
index d11d65fe41c..747ca1bc3da 100644
--- a/packages/yew-router/src/switch.rs
+++ b/packages/yew-router/src/switch.rs
@@ -56,7 +56,8 @@ pub enum Msg {
/// A Switch that dispatches route among variants of a [`Routable`].
///
-/// When a route can't be matched, it looks for the route with `not_found` attribute.
+/// When a route can't be matched, including when the path is matched but the deserialization fails,
+/// it looks for the route with `not_found` attribute.
/// If such a route is provided, it redirects to the specified route.
/// Otherwise `html! {}` is rendered and a message is logged to console
/// stating that no route can be matched.
diff --git a/website/docs/concepts/router.md b/website/docs/concepts/router.md
index 53d6176b0d9..bba9f674eb3 100644
--- a/website/docs/concepts/router.md
+++ b/website/docs/concepts/router.md
@@ -3,12 +3,20 @@ title: "Router"
description: "Yew's official router"
---
-[The router on crates.io](https://crates.io/crates/yew-router)
+Routers in Single Page Applications (SPA) handle displaying different pages depending on what the URL is. Instead of the
+default behavior of requesting a different remote resource when a link is clicked, the router instead sets the URL
+locally to point to a valid route in your application. The router then detects this change and then decides what to
+render.
-Routers in Single Page Applications (SPA) handle displaying different pages depending on what the URL is.
-Instead of the default behavior of requesting a different remote resource when a link is clicked,
-the router instead sets the URL locally to point to a valid route in your application.
-The router then detects this change and then decides what to render.
+Yew provides router support in the `yew-router` crate. To start using it, add the dependency to your `Cargo.toml`
+
+
+
+```toml
+yew-router = { git = "https://github.com/yewstack/yew.git" }
+```
+
+The utilities needed are provided under `yew_router::prelude`,
## Usage
@@ -31,65 +39,18 @@ enum Route {
}
```
-A `Route` is paired with a `` component, which finds the first variant whose path matches the
-browser's current URL and passes it to the `render` callback. The callback then decides what to render.
-In case no path is matched, the router navigates to the path with `not_found` attribute. If no route is specified,
-nothing is rendered, and a message is logged to console stating that no route was matched.
-
-```rust
-use yew_router::prelude::*;;
-use yew::prelude::*;
-
-#[derive(Clone, Routable, PartialEq)]
-enum Route {
- #[at("/")]
- Home,
- #[at("/secure")]
- Secure,
- #[not_found]
- #[at("/404")]
- NotFound,
-}
-
-#[function_component(Secure)]
-fn secure() -> Html {
- let history = use_history().unwrap();
-
- let onclick_callback = Callback::from(move |_| history.push(Route::Home));
- html! {
-
-
{ "Secure" }
-
-
- }
-}
-
-fn switch(routes: &Route) -> Html {
- match routes {
- Route::Home => html! {
},
- }
-}
-
-#[function_component(Main)]
-fn app() -> Html {
- html! {
- render={Switch::render(switch)} />
- }
-}
-```
+A `Route` is paired with a `` component, which finds the variant whose path matches the browser's
+current URL and passes it to the `render` callback. The callback then decides what to render. In case no path is
+matched, the router navigates to the path with `not_found` attribute. If no route is specified, nothing is rendered, and
+a message is logged to console stating that no route was matched.
Finally, you need to register the `` component as a context.
`` provides session history information to its children.
-When using `yew-router` in browser environment, `` is
-recommended.
+When using `yew-router` in browser environment, `` is recommended.
```rust
-use yew_router::prelude::*;;
+use yew_router::prelude::*;
use yew::prelude::*;
#[derive(Clone, Routable, PartialEq)]
@@ -107,11 +68,11 @@ enum Route {
fn secure() -> Html {
let history = use_history().unwrap();
- let onclick_callback = Callback::from(move |_| history.push(Route::Home));
+ let onclick = Callback::once(move |_| history.push(Route::Home));
html! {
{ "Secure" }
-
+
}
}
@@ -141,7 +102,8 @@ fn app() -> Html {
It is also possible to extract information from a route.
```rust
-# use yew_router::prelude::*;
+use yew_router::prelude::*;
+
#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
@@ -164,26 +126,52 @@ fn switch(routes: &Route) -> Html {
}
```
-Linking to a specific post is as easy as passing the variant to `Link`:
+:::note
+You can have a normal `Post` variant instead of `Post {id: String}` too. For example when `Post` is rendered
+with another router, the field can then be redundant as the other router is able to match and handle the path. See the
+[Nested Router](#nested-router) section below for details
+:::
+
+Note the fields must implement `Clone + PartialEq` as part of the `Route` enum. They must also implement
+`std::fmt::Display` and `std::str::FromStr` for serialization and deserialization. Primitive types like integer, float,
+and String already satisfy the requirements.
+
+In case when the form of the path matches, but the deserialization fails (as per `FromStr`). The router will consider
+the route as unmatched and try to render the not found route (or a blank page if the not found route is unspecified).
+
+Consider this example:
```rust ,ignore
- to={Route::Post { id: "new-yew-release".to_string() }}>{ "Yew v0.19 out now!" }>
+#[derive(Clone, Routable, PartialEq)]
+enum Route {
+ #[at("/news/:id")]
+ News { id: u8 },
+ #[not_found]
+ #[at("/404")]
+ NotFound,
+}
+// switch function renders News and id as is. Omitted here.
```
-For more information about the route syntax and how to bind parameters, check out [route-recognizer](https://docs.rs/route-recognizer/0.3.1/route_recognizer/#routing-params).
+When the segment goes over 255, `u8::from_str()` fails with `ParseIntError`, the router will then consider the route
+unmatched.
+
+
+
+For more information about the route syntax and how to bind parameters, check
+out [route-recognizer](https://docs.rs/route-recognizer/0.3.1/route_recognizer/#routing-params).
### History and Location
-The router provides a universal `History` and `Location` struct which
-can be used to access routing information. They can be retrieved by
-hooks or convenient functions on `ctx.link()`.
+The router provides a universal `History` and `Location` struct which can be used to access routing information. They
+can be retrieved by hooks or convenient functions on `ctx.link()`.
They have a couple flavours:
#### `AnyHistory` and `AnyLocation`
-These types are available with all routers and should be used whenever possible.
-They implement a subset of `window.history` and `window.location`.
+These types are available with all routers and should be used whenever possible. They implement a subset
+of `window.history` and `window.location`.
You can access them using the following hooks:
@@ -192,27 +180,174 @@ You can access them using the following hooks:
#### `BrowserHistory` and `BrowserLocation`
-These are only available when `` is used. They provide
-additional functionality that is not available in `AnyHistory` and
+These are only available when `` is used. They provide additional functionality that is not available
+in `AnyHistory` and
`AnyLocation` (such as: `location.host`).
### Navigation
-To navigate between pages, use either a `Link` component (which renders a `` element), the `history.push` function, or the `history.replace` function, which replaces the current page in the user's browser history instead of pushing a new one onto the stack.
+`yew_router` provides a handful of tools to work with navigation.
+
+#### Link
+
+A `` renders as an `` element, the `onclick` event handler will call
+[preventDefault](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault), and push the targeted page to the
+history and render the desired page, which is what should be expected from a Single Page App. The default onclick of a
+normal anchor element would reload the page.
+
+The `` element also passes its children to the `` element. Consider it a replacement of `` for in-app
+routes. Except you supply a `to` attribute instead of a `href`. An example usage:
+
+```rust ,ignore
+ to={Route::Home}>{ "click here to go home" }>
+```
+
+Struct variants work as expected too:
+
+```rust ,ignore
+ to={Route::Post { id: "new-yew-release".to_string() }}>{ "Yew v0.19 out now!" }>
+```
+
+#### History API
+
+History API is provided for both function components and struct components. They can enable callbacks to change the
+route. An `AnyHistory` instance can be obtained in either cases to manipulate the route.
+
+##### Function Components
+
+For function components, the `use_history` hook re-renders the component and returns the current route whenever the
+history changes. Here's how to implement a button that navigates to the `Home` route when clicked.
+
+```rust ,ignore
+#[function_component(MyComponent)]
+pub fn my_component() -> Html {
+ let history = use_history().unwrap();
+ let onclick = Callback::once(move |_| history.push(Route::Home));
+
+ html! {
+ <>
+
+ >
+ }
+}
+```
+
+:::tip
+The example here uses `Callback::once`. Use a normal callback if the target route can be the same with the route
+the component is in. For example when you have a logo button on every page the that goes back to home when clicked,
+clicking that button twice on home page causes the code to panic because the second click pushes an identical Home route
+and won't trigger a re-render of the element.
+
+In other words, only use `Callback::once` when you are sure the target route is different. Or use normal callbacks only
+to be safe.
+:::
+
+If you want to replace the current history instead of pushing a new history onto the stack, use `history.replace()`
+instead of `history.push()`.
+
+You may notice `history` has to move into the callback, so it can't be used again for other callbacks. Luckily `history`
+implements `Clone`, here's for example how to have multiple buttons to different routes:
+
+```rust ,ignore
+use yew::prelude::*;
+use yew_router::prelude::*;
+
+#[function_component(NavItems)]
+pub fn nav_items() -> Html {
+ let history = use_history().unwrap();
+
+ let go_home_button = {
+ let history = history.clone();
+ let onclick = Callback::once(move |_| history.push(Route::Home));
+ html! {
+
+ }
+ };
+
+ let go_to_first_post_button = {
+ let history = history.clone();
+ let onclick = Callback::once(move |_| history.push(Route::Post { id: "first-post".to_string() }));
+ html! {
+
+ }
+ };
+
+ let go_to_secure_button = {
+ let onclick = Callback::once(move |_| history.push(Route::Secure));
+ html! {
+
+ }
+ };
+
+ html! {
+ <>
+ {go_home_button}
+ {go_to_first_post_button}
+ {go_to_secure_button}
+ >
+ }
+}
+```
+
+##### Struct Components
+
+For struct components, the `AnyHistory` instance can be obtained through the `ctx.link().history()` API. The rest is
+identical with the function component case. Here's an example of a view function that renders a single button.
+
+```rust ,ignore
+fn view(&self, ctx: &Context) -> Html {
+ let history = ctx.link().history().unwrap();
+ let onclick = Callback::once(move |_| history.push(MainRoute::Home));
+ html!{
+
+ }
+}
+```
+
+#### Redirect
+
+`yew-router` also provides a `` element in the prelude. It can be used to achieve similar effects as the
+history API. The element accepts a
+`to` attribute as the target route. When a `` element is rendered, it internally calls `history.push()` and
+changes the route. Here is an example:
+
+```rust ,ignore
+#[function_component(SomePage)]
+fn some_page() -> Html {
+ // made-up hook `use_user`
+ let user = match use_user() {
+ Some(user) => user,
+ // an early return that redirects to the login page
+ // technicality: `Redirect` actually renders an empty html. But since it also pushes history, the target page
+ // shows up immediately. Consider it a "side-effect" component.
+ None => return html! {
+ to={Route::Login}/>
+ },
+ };
+ // ... actual page content.
+}
+```
+
+:::tip `Redirect` vs `history`, which to use
+The history API is the only way to manipulate route in callbacks.
+While `` can be used as return values in a component. You might also want to use `` in other
+non-component context, for example in the switch function of a [Nested Router](#nested-router).
+:::
### Listening to Changes
-#### Functional components
+#### Function Components
-Simply use available hooks `use_history`, `use_location` and `use_route`.
-Your components will re-render when provided values change.
+Alongside the `use_history` hook, there are also `use_location` and `use_route`. Your components will re-render when
+provided values change.
-#### Struct components
+#### Struct Components
In order to react on route changes, you can pass a callback closure to the `listen()` method of `AnyHistory`.
:::note
-The history listener will get unregistered once it is dropped. Make sure to store the handle inside your component state.
+The history listener will get unregistered once it is dropped. Make sure to store the handle inside your
+component state.
:::
```rust ,ignore
@@ -229,18 +364,115 @@ fn create(ctx: &Context) -> Self {
}
```
+`ctx.link().location()` and `ctx.link().route::()` can also be used to retrieve the location and the route once.
+
### Query Parameters
#### Specifying query parameters when navigating
-In order to specify query parameters when navigating to a new route, use either `history.push_with_query` or the `history.replace_with_query` functions.
-It uses `serde` to serialize the parameters into query string for the URL so any type that implements `Serialize` can be passed.
-In its simplest form this is just a `HashMap` containing string pairs.
+In order to specify query parameters when navigating to a new route, use either `history.push_with_query` or
+the `history.replace_with_query` functions. It uses `serde` to serialize the parameters into query string for the URL so
+any type that implements `Serialize` can be passed. In its simplest form this is just a `HashMap` containing string
+pairs.
#### Obtaining query parameters for current route
-`location.query` is used to obtain the query parameters.
-It uses `serde` to deserialize the parameters from query string in the URL.
+`location.query` is used to obtain the query parameters. It uses `serde` to deserialize the parameters from query string
+in the URL.
+
+## Nested Router
+
+Nested router can be useful when the app grows larger. Consider the following router structure:
+
+
+
+
+
+The nested `SettingsRouter` handles all urls that start with `/settings`. Additionally, it redirects urls that are not
+matched to the main `NotFound` route. So `/settings/gibberish` will redirect to `/404`.
+
+It can be implemented with the following code:
+
+```rust
+use yew::prelude::*;
+use yew_router::prelude::*;
+
+#[derive(Clone, Routable, PartialEq)]
+enum MainRoute {
+ #[at("/")]
+ Home,
+ #[at("/news")]
+ News,
+ #[at("/contact")]
+ Contact,
+ #[at("/settings/:s")]
+ Settings,
+ #[not_found]
+ #[at("/404")]
+ NotFound,
+}
+
+#[derive(Clone, Routable, PartialEq)]
+enum SettingsRoute {
+ #[at("/settings/profile")]
+ Profile,
+ #[at("/settings/friends")]
+ Friends,
+ #[at("/settings/theme")]
+ Theme,
+ #[not_found]
+ #[at("/settings/404")]
+ NotFound,
+}
+
+fn switch_main(route: &MainRoute) -> Html {
+ match route {
+ MainRoute::Home => html! {