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.

Yew provides router support in the yew-router crate. To start using it, add the dependency to your Cargo.toml

  1. yew-router = "0.17"

The utilities needed are provided under yew_router::prelude,

Usage

You start by defining a Route.

Routes are defined as an enum which derives Routable. This enum must be Clone + PartialEq.

  1. use yew_router::prelude::*;
  2. #[derive(Clone, Routable, PartialEq)]
  3. enum Route {
  4. #[at("/")]
  5. Home,
  6. #[at("/secure")]
  7. Secure,
  8. #[not_found]
  9. #[at("/404")]
  10. NotFound,
  11. }

A Route is paired with a <Switch /> 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.

Most of yew-router’s components, in particular <Link /> and <Switch />, must be (grand-)children of one of the Router components (e.g. <BrowserRouter />). You usually only need a single Router in your app, most often rendered immediately by your most top-level <App /> component. The Router registers a context, which is needed for Links and Switches to function. An example is shown below.

Router - 图1caution

When using yew-router in browser environment, <BrowserRouter /> is highly recommended. You can find other router flavours in the API Reference.

  1. use yew_router::prelude::*;
  2. use yew::prelude::*;
  3. #[derive(Clone, Routable, PartialEq)]
  4. enum Route {
  5. #[at("/")]
  6. Home,
  7. #[at("/secure")]
  8. Secure,
  9. #[not_found]
  10. #[at("/404")]
  11. NotFound,
  12. }
  13. #[function_component(Secure)]
  14. fn secure() -> Html {
  15. let navigator = use_navigator().unwrap();
  16. let onclick = Callback::from(move |_| navigator.push(&Route::Home));
  17. html! {
  18. <div>
  19. <h1>{ "Secure" }</h1>
  20. <button {onclick}>{ "Go Home" }</button>
  21. </div>
  22. }
  23. }
  24. fn switch(routes: Route) -> Html {
  25. match routes {
  26. Route::Home => html! { <h1>{ "Home" }</h1> },
  27. Route::Secure => html! {
  28. <Secure />
  29. },
  30. Route::NotFound => html! { <h1>{ "404" }</h1> },
  31. }
  32. }
  33. #[function_component(Main)]
  34. fn app() -> Html {
  35. html! {
  36. <BrowserRouter>
  37. <Switch<Route> render={switch} /> // <- must be child of <BrowserRouter>
  38. </BrowserRouter>
  39. }
  40. }

Path Segments

It is also possible to extract information from a route using dynamic and named wildcard segments. You can then access the post’s id inside <Switch /> and forward it to the appropriate component via properties.

  1. use yew::prelude::*;
  2. use yew_router::prelude::*;
  3. #[derive(Clone, Routable, PartialEq)]
  4. enum Route {
  5. #[at("/")]
  6. Home,
  7. #[at("/post/:id")]
  8. Post { id: String },
  9. #[at("/*path")]
  10. Misc { path: String },
  11. }
  12. fn switch(route: Route) -> Html {
  13. match route {
  14. Route::Home => html! { <h1>{ "Home" }</h1> },
  15. Route::Post { id } => html! {<p>{format!("You are looking at Post {}", id)}</p>},
  16. Route::Misc { path } => html! {<p>{format!("Matched some other path: {}", path)}</p>},
  17. }
  18. }

Router - 图2note

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 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:

  1. #[derive(Clone, Routable, PartialEq)]
  2. enum Route {
  3. #[at("/news/:id")]
  4. News { id: u8 },
  5. #[not_found]
  6. #[at("/404")]
  7. NotFound,
  8. }
  9. // switch function renders News and id as is. Omitted here.

When the segment goes over 255, u8::from_str() fails with ParseIntError, the router will then consider the route unmatched.

router deserialization failure behavior

For more information about the route syntax and how to bind parameters, check out route-recognizer.

Location

The router provides a universal Location struct via context which can be used to access routing information. They can be retrieved by hooks or convenient functions on ctx.link().

Navigation

yew_router provides a handful of tools to work with navigation.

A <Link /> renders as an <a> element, the onclick event handler will call 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 <Link /> component also passes its children to the <a> element. Consider it a replacement of <a/> for in-app routes. Except you supply a to attribute instead of a href. An example usage:

  1. <Link<Route> to={Route::Home}>{ "click here to go home" }</Link<Route>>

Struct variants work as expected too:

  1. <Link<Route> to={Route::Post { id: "new-yew-release".to_string() }}>{ "Yew v0.19 out now!" }</Link<Route>>

Navigator API

Navigator API is provided for both function components and struct components. They enable callbacks to change the route. An Navigator instance can be obtained in either cases to manipulate the route.

Function Components

For function components, the use_navigator hook re-renders the component when the underlying navigator provider changes. Here’s how to implement a button that navigates to the Home route when clicked.

  1. #[function_component(MyComponent)]
  2. pub fn my_component() -> Html {
  3. let navigator = use_navigator().unwrap();
  4. let onclick = Callback::from(move |_| navigator.push(&Route::Home));
  5. html! {
  6. <>
  7. <button {onclick}>{"Click to go home"}</button>
  8. </>
  9. }
  10. }

Router - 图4caution

The example here uses Callback::from. Use a normal callback if the target route can be the same with the route the component is in, or just to play safe. For example, when you have a logo button on every page, 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 the use_navigator hook won’t trigger a re-render.

If you want to replace the current location instead of pushing a new location onto the stack, use navigator.replace() instead of navigator.push().

You may notice navigator has to move into the callback, so it can’t be used again for other callbacks. Luckily navigator implements Clone, here’s for example how to have multiple buttons to different routes:

  1. use yew::prelude::*;
  2. use yew_router::prelude::*;
  3. #[function_component(NavItems)]
  4. pub fn nav_items() -> Html {
  5. let navigator = use_navigator().unwrap();
  6. let go_home_button = {
  7. let navigator = navigator.clone();
  8. let onclick = Callback::from(move |_| navigator.push(&Route::Home));
  9. html! {
  10. <button {onclick}>{"click to go home"}</button>
  11. }
  12. };
  13. let go_to_first_post_button = {
  14. let navigator = navigator.clone();
  15. let onclick = Callback::from(move |_| navigator.push(&Route::Post { id: "first-post".to_string() }));
  16. html! {
  17. <button {onclick}>{"click to go the first post"}</button>
  18. }
  19. };
  20. let go_to_secure_button = {
  21. let onclick = Callback::from(move |_| navigator.push(&Route::Secure));
  22. html! {
  23. <button {onclick}>{"click to go to secure"}</button>
  24. }
  25. };
  26. html! {
  27. <>
  28. {go_home_button}
  29. {go_to_first_post_button}
  30. {go_to_secure_button}
  31. </>
  32. }
  33. }
Struct Components

For struct components, the Navigator instance can be obtained through the ctx.link().navigator() API. The rest is identical with the function component case. Here’s an example of a view function that renders a single button.

  1. fn view(&self, ctx: &Context<Self>) -> Html {
  2. let navigator = ctx.link().navigator().unwrap();
  3. let onclick = Callback::from(move |_| navigator.push(&MainRoute::Home));
  4. html!{
  5. <button {onclick}>{"Go Home"}</button>
  6. }
  7. }

Redirect

yew-router also provides a <Redirect /> component in the prelude. It can be used to achieve similar effects as the navigator API. The component accepts a to attribute as the target route. When a <Redirect/> is rendered users will be redirect to the route specified in props. Here is an example:

  1. #[function_component(SomePage)]
  2. fn some_page() -> Html {
  3. // made-up hook `use_user`
  4. let user = match use_user() {
  5. Some(user) => user,
  6. // Redirects to the login page when user is `None`.
  7. None => return html! {
  8. <Redirect<Route> to={Route::Login}/>
  9. },
  10. };
  11. // ... actual page content.
  12. }

Router - 图5Redirect vs Navigator, which to use

The Navigator API is the only way to manipulate route in callbacks. While <Redirect /> can be used as return values in a component. You might also want to use <Redirect /> in other non-component context, for example in the switch function of a Nested Router.

Listening to Changes

Function Components

You can use use_location and use_route hooks. Your components will re-render when provided values change.

Struct Components

In order to react on route changes, you can pass a callback closure to the add_location_listener() method of ctx.link().

Router - 图6note

The location listener will get unregistered once it is dropped. Make sure to store the handle inside your component state.

  1. fn create(ctx: &Context<Self>) -> Self {
  2. let listener = ctx.link()
  3. .add_location_listener(ctx.link().callback(
  4. // handle event
  5. ))
  6. .unwrap();
  7. MyComponent {
  8. _listener: listener
  9. }
  10. }

ctx.link().location() and ctx.link().route::<R>() 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 navigator.push_with_query or the navigator.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.

Nested Router

Nested router can be useful when the app grows larger. Consider the following router structure:

nested router structurenested 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.

Router - 图9caution

Though note that this is still work in progress so the way we do this is not final

It can be implemented with the following code:

  1. use yew::prelude::*;
  2. use yew_router::prelude::*;
  3. use gloo::utils::window;
  4. use wasm_bindgen::UnwrapThrowExt;
  5. #[derive(Clone, Routable, PartialEq)]
  6. enum MainRoute {
  7. #[at("/")]
  8. Home,
  9. #[at("/news")]
  10. News,
  11. #[at("/contact")]
  12. Contact,
  13. #[at("/settings")]
  14. SettingsRoot,
  15. #[at("/settings/*")]
  16. Settings,
  17. #[not_found]
  18. #[at("/404")]
  19. NotFound,
  20. }
  21. #[derive(Clone, Routable, PartialEq)]
  22. enum SettingsRoute {
  23. #[at("/settings")]
  24. Profile,
  25. #[at("/settings/friends")]
  26. Friends,
  27. #[at("/settings/theme")]
  28. Theme,
  29. #[not_found]
  30. #[at("/settings/404")]
  31. NotFound,
  32. }
  33. fn switch_main(route: MainRoute) -> Html {
  34. match route {
  35. MainRoute::Home => html! {<h1>{"Home"}</h1>},
  36. MainRoute::News => html! {<h1>{"News"}</h1>},
  37. MainRoute::Contact => html! {<h1>{"Contact"}</h1>},
  38. MainRoute::SettingsRoot | MainRoute::Settings => html! { <Switch<SettingsRoute> render={switch_settings} /> },
  39. MainRoute::NotFound => html! {<h1>{"Not Found"}</h1>},
  40. }
  41. }
  42. fn switch_settings(route: SettingsRoute) -> Html {
  43. match route {
  44. SettingsRoute::Profile => html! {<h1>{"Profile"}</h1>},
  45. SettingsRoute::Friends => html! {<h1>{"Friends"}</h1>},
  46. SettingsRoute::Theme => html! {<h1>{"Theme"}</h1>},
  47. SettingsRoute::NotFound => html! {<Redirect<MainRoute> to={MainRoute::NotFound}/>}
  48. }
  49. }
  50. #[function_component(App)]
  51. pub fn app() -> Html {
  52. html! {
  53. <BrowserRouter>
  54. <Switch<MainRoute> render={switch_main} />
  55. </BrowserRouter>
  56. }
  57. }

Basename

It’s possible to define a basename with yew-router. A basename is a common prefix of all routes. Both the Navigator API and <Switch /> component respect basename setting. All pushed routes will be prefixed with the basename and all switches will strip the basename before trying to parse the path into a Routable.

If a basename prop is not supplied to the Router component, it will use the href attribute of the <base /> element in your html file and fallback to / if no <base /> presents in the html file.

Relevant examples

API Reference