dependabot[bot] 7ea787285e
Bump the website-deps group across 1 directory with 15 updates (#3798)
* Bump the website-deps group across 1 directory with 15 updates

Bumps the website-deps group with 14 updates in the /website directory:

| Package | From | To |
| --- | --- | --- |
| [@docusaurus/core](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus) | `3.5.2` | `3.7.0` |
| [@docusaurus/plugin-client-redirects](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-client-redirects) | `3.5.2` | `3.7.0` |
| [@docusaurus/preset-classic](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-preset-classic) | `3.5.2` | `3.7.0` |
| [@mdx-js/react](https://github.com/mdx-js/mdx/tree/HEAD/packages/react) | `3.0.1` | `3.1.0` |
| [docusaurus-plugin-sass](https://github.com/rlamana/docusaurus-plugin-sass) | `0.2.5` | `0.2.6` |
| [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `18.3.1` | `19.0.0` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `18.3.10` | `19.0.8` |
| [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `18.3.1` | `19.0.0` |
| [sass](https://github.com/sass/dart-sass) | `1.79.4` | `1.83.4` |
| [@docusaurus/tsconfig](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-tsconfig) | `3.5.2` | `3.7.0` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.8.0` | `8.22.0` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.8.0` | `8.22.0` |
| [prettier](https://github.com/prettier/prettier) | `3.3.3` | `3.4.2` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.6.2` | `5.7.3` |



Updates `@docusaurus/core` from 3.5.2 to 3.7.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.7.0/packages/docusaurus)

Updates `@docusaurus/plugin-client-redirects` from 3.5.2 to 3.7.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.7.0/packages/docusaurus-plugin-client-redirects)

Updates `@docusaurus/preset-classic` from 3.5.2 to 3.7.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.7.0/packages/docusaurus-preset-classic)

Updates `@mdx-js/react` from 3.0.1 to 3.1.0
- [Release notes](https://github.com/mdx-js/mdx/releases)
- [Changelog](https://github.com/mdx-js/mdx/blob/main/changelog.md)
- [Commits](https://github.com/mdx-js/mdx/commits/3.1.0/packages/react)

Updates `docusaurus-plugin-sass` from 0.2.5 to 0.2.6
- [Release notes](https://github.com/rlamana/docusaurus-plugin-sass/releases)
- [Commits](https://github.com/rlamana/docusaurus-plugin-sass/compare/v0.2.5...v0.2.6)

Updates `react` from 18.3.1 to 19.0.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.0.0/packages/react)

Updates `@types/react` from 18.3.10 to 19.0.8
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `react-dom` from 18.3.1 to 19.0.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.0.0/packages/react-dom)

Updates `sass` from 1.79.4 to 1.83.4
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.79.4...1.83.4)

Updates `@docusaurus/module-type-aliases` from 3.5.2 to 3.7.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.7.0/packages/docusaurus-module-type-aliases)

Updates `@docusaurus/tsconfig` from 3.5.2 to 3.7.0
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.7.0/packages/docusaurus-tsconfig)

Updates `@types/react` from 18.3.10 to 19.0.8
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@typescript-eslint/eslint-plugin` from 8.8.0 to 8.22.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.22.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.8.0 to 8.22.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.22.0/packages/parser)

Updates `prettier` from 3.3.3 to 3.4.2
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.3.3...3.4.2)

Updates `typescript` from 5.6.2 to 5.7.3
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.6.2...v5.7.3)

---
updated-dependencies:
- dependency-name: "@docusaurus/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: website-deps
- dependency-name: "@docusaurus/plugin-client-redirects"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: website-deps
- dependency-name: "@docusaurus/preset-classic"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: website-deps
- dependency-name: "@mdx-js/react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: website-deps
- dependency-name: docusaurus-plugin-sass
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: website-deps
- dependency-name: react
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: website-deps
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: website-deps
- dependency-name: react-dom
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: website-deps
- dependency-name: sass
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: website-deps
- dependency-name: "@docusaurus/module-type-aliases"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: website-deps
- dependency-name: "@docusaurus/tsconfig"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: website-deps
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: website-deps
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: website-deps
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: website-deps
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: website-deps
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: website-deps
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: fix formatting

* chore: bump prettier (can't reproduce element.mdx warning locally?)

* workflow: show formatting diff for locally unproduceable errors

* chore: format elements.mdx

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Yan <syan4@ualberta.ca>
Co-authored-by: Siyuan Yan <44753941+Madoshakalaka@users.noreply.github.com>
2025-02-21 07:33:40 +09:00

504 lines
16 KiB
Plaintext

---
title: 'Router'
description: "Yew's official 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`
```toml
yew-router = "0.16"
```
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`.
```rust
use yew_router::prelude::*;
#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
Home,
#[at("/secure")]
Secure,
#[not_found]
#[at("/404")]
NotFound,
}
```
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.
:::caution
When using `yew-router` in browser environment, `<BrowserRouter />` is highly recommended.
You can find other router flavours in the [API Reference](https://docs.rs/yew-router/).
:::
```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::once(move |_| history.push(Route::Home));
html! {
<div>
<h1>{ "Secure" }</h1>
<button {onclick}>{ "Go Home" }</button>
</div>
}
}
fn switch(routes: &Route) -> Html {
match routes {
Route::Home => html! { <h1>{ "Home" }</h1> },
Route::Secure => html! {
<Secure />
},
Route::NotFound => html! { <h1>{ "404" }</h1> },
}
}
#[function_component(Main)]
fn app() -> Html {
html! {
<BrowserRouter>
<Switch<Route> render={Switch::render(switch)} /> // <- must be child of <BrowserRouter>
</BrowserRouter>
}
}
```
### Path Segments
It is also possible to extract information from a route.
You can then access the post's id inside `<Switch />` and forward it to the appropriate component via properties.
```rust
use yew::prelude::*;
use yew_router::prelude::*;
#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
Home,
#[at("/post/:id")]
Post { id: String },
}
fn switch(route: &Route) -> Html {
match route {
Route::Home => html! { <h1>{ "Home" }</h1> },
Route::Post { id } => html! {<p>{format!("You are looking at Post {}", id)}</p>},
}
}
```
:::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
#[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.
```
When the segment goes over 255, `u8::from_str()` fails with `ParseIntError`, the router will then consider the route
unmatched.
![router deserialization failure behavior](/img/router-deserialization-failure-behavior.gif)
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()`.
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`.
You can access them using the following hooks:
- `use_history`
- `use_location`
#### `BrowserHistory` and `BrowserLocation`
These are only available when `<BrowserRouter />` is used. They provide additional functionality that is not available
in `AnyHistory` and
`AnyLocation` (such as: `location.host`).
### Navigation
`yew_router` provides a handful of tools to work with navigation.
#### Link
A `<Link/>` renders as an `<a>` 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 `<Link/>` element 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:
```rust ,ignore
<Link<Route> to={Route::Home}>{ "click here to go home" }</Link<Route>>
```
Struct variants work as expected too:
```rust ,ignore
<Link<Route> to={Route::Post { id: "new-yew-release".to_string() }}>{ "Yew v0.19 out now!" }</Link<Route>>
```
#### 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! {
<>
<button {onclick}>{"Click to go home"}</button>
</>
}
}
```
:::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! {
<button {onclick}>{"click to go home"}</button>
}
};
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! {
<button {onclick}>{"click to go the first post"}</button>
}
};
let go_to_secure_button = {
let onclick = Callback::once(move |_| history.push(Route::Secure));
html! {
<button {onclick}>{"click to go to secure"}</button>
}
};
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<Self>) -> Html {
let history = ctx.link().history().unwrap();
let onclick = Callback::once(move |_| history.push(MainRoute::Home));
html!{
<button {onclick}>{"Go Home"}</button>
}
}
```
#### Redirect
`yew-router` also provides a `<Redirect/>` 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 `<Redirect/>` 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! {
<Redirect<Route> 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 `<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](#nested-router).
:::
### Listening to Changes
#### Function Components
Alongside the `use_history` hook, there are also `use_location` and `use_route`. 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_history_listener()` method of `ctx.link()`.
:::note
The history listener will get unregistered once it is dropped. Make sure to store the handle inside your
component state.
:::
```rust ,ignore
fn create(ctx: &Context<Self>) -> Self {
let listener = ctx.link()
.add_history_listener(ctx.link().callback(
// handle event
))
.unwrap();
MyComponent {
_listener: listener
}
}
```
`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 `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.
## Nested Router
Nested router can be useful when the app grows larger. Consider the following router structure:
<!--
The graph is produced with the following code, with graphviz.
To reproduce. Save the code in a file, say `input.dot`,
And run `$ dot -Tsvg input.dot -o nested-router.svg`
digraph {
bgcolor=transparent
node [shape=box style="filled, rounded" fillcolor=white]
Home; News; Contact; "Not Found"; Profile; Friends; Theme; SettingsNotFound [label="Not Found"];
node [fillcolor=lightblue style="filled, rounded"]
"Main Router"; "Settings Router";
"Main Router" -> {Home News Contact "Not Found" "Settings Router"} [arrowhead=none]
"Settings Router" -> {SettingsNotFound Profile Friends Theme } [arrowhead=none]
SettingsNotFound -> "Not Found" [constraint=false]
}
-->
<!--
Also the dark themed version:
digraph {
bgcolor=transparent
node [shape=box style="filled, rounded" fillcolor=grey color=white fontcolor=white]
Home; News; Contact; "Not Found"; Profile; Friends; Theme; SettingsNotFound [label="Not Found"];
node [fillcolor=lightblue style="filled, rounded" color=white fontcolor=black]
"Main Router"; "Settings Router";
"Main Router" -> {Home News Contact "Not Found" "Settings Router"} [arrowhead=none color=white]
"Settings Router" -> {SettingsNotFound Profile Friends Theme } [arrowhead=none color=white]
SettingsNotFound -> "Not Found" [constraint=false color=white]
}
-->
import useBaseUrl from '@docusaurus/useBaseUrl'
import ThemedImage from '@theme/ThemedImage'
<ThemedImage
alt="nested router structure"
sources={{
light: useBaseUrl('/img/nested-router-light.svg'),
dark: useBaseUrl('/img/nested-router-dark.svg'),
}}
/>
;
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! {<h1>{"Home"}</h1>},
MainRoute::News => html! {<h1>{"News"}</h1>},
MainRoute::Contact => html! {<h1>{"Contact"}</h1>},
MainRoute::Settings => html! {
<Switch<SettingsRoute> render={Switch::render(switch_settings)} />
},
MainRoute::NotFound => html! {<h1>{"Not Found"}</h1>},
}
}
fn switch_settings(route: &SettingsRoute) -> Html {
match route {
SettingsRoute::Profile => html! {<h1>{"Profile"}</h1>},
SettingsRoute::Friends => html! {<h1>{"Friends"}</h1>},
SettingsRoute::Theme => html! {<h1>{"Theme"}</h1>},
SettingsRoute::NotFound => html! {
<Redirect<MainRoute> to={MainRoute::NotFound}/>
}
}
}
#[function_component(App)]
pub fn app() -> Html {
html! {
<BrowserRouter>
<Switch<MainRoute> render={Switch::render(switch_main)} />
</BrowserRouter>
}
}
```
## Relevant examples
- [Router](https://github.com/yewstack/yew/tree/yew-v0.19.3/examples/router)