diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 64e937eafc9..6ad20d12374 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -158,6 +158,11 @@ jobs: cd packages/yew wasm-pack test --chrome --firefox --headless -- --features "wasm_test" + - name: Run tests - yew-router + run: | + cd packages/yew-router + wasm-pack test --chrome --firefox --headless + - name: Run tests - yew-functional run: | cd packages/yew-functional diff --git a/Cargo.toml b/Cargo.toml index 227bc064108..9edeaa9e73f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ members = [ # Router "packages/yew-router", "packages/yew-router-macro", - "packages/yew-router-route-parser", # Function components "packages/yew-functional", diff --git a/docs/concepts/router.md b/docs/concepts/router.md index 0a00e8485aa..99a1b381f1b 100644 --- a/docs/concepts/router.md +++ b/docs/concepts/router.md @@ -5,87 +5,79 @@ 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. -## Core Elements +## Usage -### `Route` - -Contains a String representing everything after the domain in the url and optionally the state stored in the history API. - -### `RouteService` - -Communicates with the browser to get and set Routes. - -### `RouteAgent` - -Owns a RouteService and is used to coordinate updates when the route changes, either from within the application logic or from an event fired from the browser. - -### `Switch` - -The `Switch` trait is used to convert a `Route` to and from the implementer of this trait. - -### `Router` - -The Router component communicates with `RouteAgent` and will automatically resolve Routes it gets from the agent into switches, which it will expose via a `render` prop that allows specifying how the resulting switch gets converted to `Html`. - -## How to use the router - -First, you want to create a type that represents all the states of your application. Do note that while this typically is an enum, structs are supported as well, and that you can nest other items that implement `Switch` inside. - -Then you should derive `Switch` for your type. For enums, every variant must be annotated with ` -#[at = "/some/route"]` (or `#[at = "/some/route"]`, but this is being phased out in favor of "at"), -or if you use a struct instead, that must appear outside the struct declaration. +The Router component. It takes in a callback and renders the HTML based on the returned value of the callback. It is usually placed +at the top of the application. +Routes are defined by an `enum` which derives `Routable`: ```rust -#[derive(Switch)] -enum AppRoute { - #[at = "/login"] - Login, - #[at = "/register"] - Register, - #[at = "/delete_account"] - Delete, - #[at = "/posts/{id}"] - ViewPost(i32), - #[at = "/posts/view"] - ViewPosts, - #[at = "/"] - Home +#[derive(Routable)] +enum Route { + #[at("/")] + Home, + #[at("/secure")] + Secure, + #[not_found] + #[at("/404")] + NotFound, } ``` -:::caution -Do note that the implementation generated by the derive macro for `Switch` will try to match each -variant in order from first to last, so if any route could possibly match two of your specified -`to` annotations, then the first one will match, and the second will never be tried. For example, -if you defined the following `Switch`, the only route that would be matched would be -`AppRoute::Home`. +The `Router` component takes the `Routable` enum as its type parameter, 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. + +`yew_router::current_route` is used to programmatically obtain the current route. +`yew_router::attach_route_listener` is used to attach a listener which is called every time route is changed. ```rust -#[derive(Switch)] -enum AppRoute { - #[at = "/"] - Home, - #[at = "/login"] - Login, - #[at = "/register"] - Register, - #[at = "/delete_account"] - Delete, - #[at = "/posts/{id}"] - ViewPost(i32), - #[at = "/posts/view"] - ViewPosts, +#[function_component(Main)] +fn app() -> Html { + html! { + render=Router::render(switch) /> + } +} + +fn switch(route: &Route) -> Html { + match route { + Route::Home => html! {

{ "Home" }

}, + Route::Secure => { + let callback = Callback::from(|_| yew_router::push_route(Routes::Home)); + html! { +
+

{ "Secure" }

+ +
+ } + }, + Route::NotFound => html! {

{ "404" }

}, + } } ``` -::: -You can also capture sections using variations of `{}` within your `#[at = ""]` annotation. `{}` means capture text until the next separator \(either "/", "?", "&", or "\#" depending on the context\). `{*}` means capture text until the following characters match, or if no characters are present, it will match anything. `{}` means capture text until the specified number of separators are encountered \(example: `{2}` will capture until two separators are encountered\). +### Navigation + +To navigate between pages, use either a `Link` component (which renders a `` element) or the `yew_router::push_route` function. + +### Query Parameters + +#### Specifying query parameters when navigating + +In order to specify query parameters when navigating to a new route, use `yew_router::push_route_with_query` function. +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. -For structs and enums with named fields, you must specify the field's name within the capture group like so: `{user_name}` or `{*:age}`. +#### Obtaining query parameters for current route -The Switch trait works with capture groups that are more structured than just Strings. You can specify any type that implements `Switch`. So you can specify that the capture group is a `usize`, and if the captured section of the URL can't be converted to it, then the variant won't match. +`yew_router::parse_query` is used to obtain the query parameters. +It uses `serde` to deserialize the parameters from query string in the URL. ## Relevant examples - [Router](https://github.com/yewstack/yew/tree/master/examples/router) diff --git a/examples/router/Cargo.toml b/examples/router/Cargo.toml index 6ef2204d25d..88738068365 100644 --- a/examples/router/Cargo.toml +++ b/examples/router/Cargo.toml @@ -15,3 +15,4 @@ yew = { path = "../../packages/yew" } yew-router = { path = "../../packages/yew-router" } yewtil = { path = "../../packages/yewtil" } yew-services = { path = "../../packages/yew-services" } +serde = { version = "1.0", features = ["derive"] } diff --git a/examples/router/src/components/author_card.rs b/examples/router/src/components/author_card.rs index f279a51e01e..eecb9e1e746 100644 --- a/examples/router/src/components/author_card.rs +++ b/examples/router/src/components/author_card.rs @@ -1,9 +1,6 @@ -use crate::{ - content::Author, - generator::Generated, - switch::{AppAnchor, AppRoute}, -}; +use crate::{content::Author, generator::Generated, Route}; use yew::prelude::*; +use yew_router::prelude::*; #[derive(Clone, Debug, PartialEq, Properties)] pub struct Props { @@ -57,9 +54,9 @@ impl Component for AuthorCard {
- + classes=classes!("card-footer-item") route=Route::Author { id: author.seed }> { "Profile" } - + >
} diff --git a/examples/router/src/components/post_card.rs b/examples/router/src/components/post_card.rs index 0d0b4b829e8..811d07fc3cf 100644 --- a/examples/router/src/components/post_card.rs +++ b/examples/router/src/components/post_card.rs @@ -1,9 +1,6 @@ -use crate::{ - content::Post, - generator::Generated, - switch::{AppAnchor, AppRoute}, -}; +use crate::{content::Post, generator::Generated, Route}; use yew::prelude::*; +use yew_router::components::Link; #[derive(Clone, Debug, PartialEq, Properties)] pub struct Props { @@ -46,12 +43,12 @@ impl Component for PostCard {
- + classes=classes!("title", "is-block") route=Route::Post { id: post.seed }> { &post.title } - - + > + classes=classes!("subtitle", "is-block") route=Route::Author { id: post.author.seed }> { &post.author.name } - + >
} diff --git a/examples/router/src/main.rs b/examples/router/src/main.rs index 09eabf2a832..26c2dab0cfb 100644 --- a/examples/router/src/main.rs +++ b/examples/router/src/main.rs @@ -1,5 +1,5 @@ use yew::prelude::*; -use yew_router::{route::Route, switch::Permissive}; +use yew_router::prelude::*; mod components; mod content; @@ -9,8 +9,23 @@ use pages::{ author::Author, author_list::AuthorList, home::Home, page_not_found::PageNotFound, post::Post, post_list::PostList, }; -mod switch; -use switch::{AppAnchor, AppRoute, AppRouter, PublicUrlSwitch}; + +#[derive(Routable, PartialEq, Clone, Debug)] +pub enum Route { + #[at("/posts/:id")] + Post { id: u64 }, + #[at("/posts")] + Posts, + #[at("/authors/:id")] + Author { id: u64 }, + #[at("/authors")] + Authors, + #[at("/")] + Home, + #[not_found] + #[at("/404")] + NotFound, +} pub enum Msg { ToggleNavbar, @@ -50,12 +65,7 @@ impl Component for Model { { self.view_nav() }
- + render=Router::render(switch) />