fix book example section

This commit is contained in:
Adrian Wannenmacher 2022-12-16 10:16:47 +01:00
parent e3610738ab
commit 1890ebc4f7
No known key found for this signature in database
GPG key ID: 19D986ECB1E492D5
5 changed files with 147 additions and 155 deletions

View file

@ -3,7 +3,7 @@ Not a bird's nest! A nest of routes!
In this chapter we will begin to build the blog portion of our site which will In this chapter we will begin to build the blog portion of our site which will
include links, nested URLs, and URL parameters. We will also explore the use include links, nested URLs, and URL parameters. We will also explore the use
case of rendering components directly in the [`Router`]. case of rendering components directly in the component calling [`use_router`].
## Site Navigation ## Site Navigation
Our site visitors won't know all the available pages and blogs on our site so we Our site visitors won't know all the available pages and blogs on our site so we
@ -44,7 +44,10 @@ fn NavBar(cx: Scope) -> Element {
nav { nav {
ul { ul {
// new stuff starts here // new stuff starts here
li { Link { target: InternalTarget(String::from("/")), "Home" } } li { Link {
target: NavigationTarget::Internal(String::from("/")),
"Home"
} }
li { Link { li { Link {
target: "/blog", // short form target: "/blog", // short form
"Blog" "Blog"
@ -72,18 +75,15 @@ And finally, we add the navbar component in our app component:
# fn PageNotFound(cx: Scope) -> Element { unimplemented!() } # fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
# #
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
let routes = use_segment(&cx, || { use_router(
Segment::new() cx,
.index(Home as Component) &|| RouterConfiguration::default(),
.fallback(PageNotFound as Component) &|| Segment::content(comp(Home)).fallback(comp(PageNotFound))
}); );
cx.render(rsx! { cx.render(rsx! {
Router {
routes: routes.clone(),
NavBar { } // this is new NavBar { } // this is new
Outlet { } Outlet { }
}
}) })
} }
``` ```
@ -107,7 +107,7 @@ fn NavBar(cx: Scope) -> Element {
nav { nav {
ul { ul {
li { Link { li { Link {
target: InternalTarget(String::from("/")), target: NavigationTarget::Internal(String::from("/")),
active_class: "active", // this is new active_class: "active", // this is new
"Home" "Home"
} } } }
@ -196,10 +196,12 @@ parameters:
# extern crate dioxus_router; # extern crate dioxus_router;
# use dioxus_router::prelude::*; # use dioxus_router::prelude::*;
# #
struct PostId;
fn BlogPost(cx: Scope) -> Element { fn BlogPost(cx: Scope) -> Element {
let route = use_route(&cx).unwrap(); let route = use_route(&cx).unwrap();
let post_id = route.parameters.get("post_id"); let post_id = route.parameter::<PostId>();
let post = post_id let post = post_id
.map(|id| id.to_string()) .map(|id| id.to_string())
.unwrap_or(String::from("unknown")); .unwrap_or(String::from("unknown"));
@ -219,34 +221,31 @@ Finally, let's tell our router about those components.
# use dioxus_router::prelude::*; # use dioxus_router::prelude::*;
# fn Blog(cx: Scope) -> Element { unimplemented!() } # fn Blog(cx: Scope) -> Element { unimplemented!() }
# fn BlogList(cx: Scope) -> Element { unimplemented!() } # fn BlogList(cx: Scope) -> Element { unimplemented!() }
# struct PostId;
# fn BlogPost(cx: Scope) -> Element { unimplemented!() } # fn BlogPost(cx: Scope) -> Element { unimplemented!() }
# fn Home(cx: Scope) -> Element { unimplemented!() } # fn Home(cx: Scope) -> Element { unimplemented!() }
# fn NavBar(cx: Scope) -> Element { unimplemented!() } # fn NavBar(cx: Scope) -> Element { unimplemented!() }
# fn PageNotFound(cx: Scope) -> Element { unimplemented!() } # fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
# #
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
let routes = use_segment(&cx, || { use_router(
Segment::default() cx,
.index(Home as Component) &|| RouterConfiguration::default(),
&|| {
Segment::content(comp(Home))
// new stuff starts here // new stuff starts here
.fixed( .fixed("blog", Route::content(comp(Blog)).nested(
"blog", Segment::content(comp(BlogList))
Route::new(Blog as Component).nested( .catch_all((comp(BlogPost), PostId { }))
Segment::default() ))
.index(BlogList as Component)
.catch_all(("post_id", BlogPost as Component))
),
)
// new stuff ends here // new stuff ends here
.fallback(PageNotFound as Component) .fallback(comp(PageNotFound))
}); }
);
cx.render(rsx! { cx.render(rsx! {
Router {
routes: routes.clone(),
NavBar { } NavBar { }
Outlet { } Outlet { }
}
}) })
} }
``` ```
@ -260,4 +259,4 @@ we will go over how navigation targets (like the one we passed to our links)
work. work.
[`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html [`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html
[`Router`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Router.html [`use_router`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html

View file

@ -6,27 +6,25 @@ fn main() {
} }
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
let routes = use_segment(&cx, || { use_router(
Segment::default() cx,
.index(Home as Component) &|| RouterConfiguration::default(),
.fixed( &|| {
"blog", Segment::content(comp(Home))
Route::new(Blog as Component).nested( .fixed("blog", Route::content(comp(Blog)).nested(
Segment::default().index(BlogList as Component).catch_all( Segment::content(comp(BlogList)).catch_all(
ParameterRoute::new("post_id", BlogPost as Component).name(BlogPost), ParameterRoute::content::<PostId>(comp(BlogPost))
), .name::<BlogPostName>()
),
) )
.fixed("myblog", "/blog") ))
.fallback(PageNotFound as Component) .fixed("myblog", "/blog") // this is new
}); .fallback(comp(PageNotFound))
}
);
cx.render(rsx! { cx.render(rsx! {
Router {
routes: routes.clone(),
NavBar {} NavBar {}
Outlet {} Outlet {}
}
}) })
} }
@ -34,7 +32,7 @@ fn NavBar(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! {
nav { nav {
ul { ul {
li { Link { target: (RootIndex, []), "Home" } } li { Link { target: named::<RootIndex>(), "Home" } }
li { Link { target: "/blog", "Blog" } } li { Link { target: "/blog", "Blog" } }
} }
} }
@ -59,21 +57,23 @@ fn BlogList(cx: Scope) -> Element {
h2 { "Choose a post" } h2 { "Choose a post" }
ul { ul {
li { Link { li { Link {
target: (BlogPost, [("post_id", String::from("1"))]), target: named::<BlogPostName>().parameter::<PostId>("1"),
"Read the first blog post" "Read the first blog post"
} } } }
li { Link { li { Link {
target: (BlogPost, [("post_id", String::from("2"))]), target: named::<BlogPostName>().parameter::<PostId>("2"),
"Read the second blog post" "Read the second blog post"
} } } }
} }
}) })
} }
struct PostId;
struct BlogPostName;
fn BlogPost(cx: Scope) -> Element { fn BlogPost(cx: Scope) -> Element {
let route = use_route(&cx).unwrap(); let route = use_route(&cx).unwrap();
let post_id = route.parameters.get("post_id"); let post_id = route.parameter::<PostId>();
let post = post_id let post = post_id
.map(|id| id.to_string()) .map(|id| id.to_string())
.unwrap_or(String::from("unknown")); .unwrap_or(String::from("unknown"));

View file

@ -3,9 +3,9 @@ In this chapter, we will start utilizing Dioxus Router and add a homepage and a
404 page to our project. 404 page to our project.
## Fundamentals ## Fundamentals
Dioxus Router works based on a [`Router`] component, a route definition in Dioxus Router works based on a [`use_router`] hook, a route definition in pure
regular rust and [`Outlet`] components. If you've ever used [Vue Router], rust and [`Outlet`] components. If you've ever used [Vue Router], you should
you should feel at home with Dioxus Router. feel right at home with Dioxus Router.
First we need an actual page to route to! Let's add a homepage component: First we need an actual page to route to! Let's add a homepage component:
```rust,no_run ```rust,no_run
@ -24,12 +24,16 @@ fn Home(cx: Scope) -> Element {
We want to use Dioxus Router to separate our application into different "pages". 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. Dioxus Router will then determine which page to render based on the URL path.
To start using Dioxus Router, we need to use the [`Router`] component. All hooks To start using Dioxus Router, we need to use the [`use_router`] hook. All other
and other components the Router provides can only be used as a descendant of hooks and components the router provides can only be used as a descendant of a
a [`Router`] component. component calling [`use_router`].
The [`use_router`] hook takes three arguments:
1. `cx`, which is a common argument for all hooks.
2. A [`RouterConfiguration`], which allows us to modify its behavior.
3. A definition of all routes the application contains, in the form of its root
[`Segment`].
However, before we can add the [`Router`] we need to describe our routes in a
type it can understand:
```rust,no_run ```rust,no_run
# // Hidden lines (like this one) make the documentation tests work. # // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus; # extern crate dioxus;
@ -39,40 +43,14 @@ use dioxus_router::prelude::*;
# fn Home(cx: Scope) -> Element { unimplemented!() } # fn Home(cx: Scope) -> Element { unimplemented!() }
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
let routes = use_segment(&cx, || { use_router(
// we want our home page component to render as an index cx,
Segment::default().index(Home as Component) &|| RouterConfiguration::default(),
}); &|| Segment::content(comp(Home))
);
cx.render(rsx! { cx.render(rsx! {
p { "Hello, Dioxus!"}
})
}
```
Now we can replace the `p { "Hello, Dioxus!" }` with our [`Router`]. We also
need to tell it where to render the content of the active route. Therefore we
nest an [`Outlet`] inside it.
```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 Home(cx: Scope) -> Element { unimplemented!() }
fn App(cx: Scope) -> Element {
let routes = use_segment(&cx, || {
Segment::default().index(Home as Component)
});
cx.render(rsx! {
// new stuff starts here
Router {
routes: routes.clone() // pass in the routes we prepared before
Outlet { } Outlet { }
}
// new stuff ends here
}) })
} }
``` ```
@ -82,8 +60,8 @@ If you head to your application's browser tab, you should now see the text
you enter a different path for the URL, nothing should be displayed. 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 This is because we told Dioxus Router to render the `Home` component only when
the URL path is `/`. The _index_ functionality we used basically emulates how the URL path is `/`. The _index_ (`Segment::content()`) functionality we used
web servers treat `index.html` files. basically emulates how web servers treat `index.html` files.
## What if a Route Doesn't Exist? ## What if a Route Doesn't Exist?
In our example Dioxus Router doesn't render anything. Many sites also have a In our example Dioxus Router doesn't render anything. Many sites also have a
@ -114,17 +92,17 @@ Now to tell Dioxus Router to render our new component when no route exists.
# fn PageNotFound(cx: Scope) -> Element { unimplemented!() } # fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
# #
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
let routes = use_segment(&cx, || { use_router(
Segment::default() cx,
.index(Home as Component) &|| RouterConfiguration::default(),
.fallback(PageNotFound as Component) // this is new &|| {
}); Segment::content(comp(Home))
.fallback(comp(PageNotFound)) // this is new
}
);
cx.render(rsx! { cx.render(rsx! {
Router {
routes: routes.clone(),
Outlet { } Outlet { }
}
}) })
} }
``` ```
@ -139,5 +117,7 @@ handle when a route doesn't exist. Next, we'll create the blog portion of our
site. We will utilize nested routes and URL parameters. site. We will utilize nested routes and URL parameters.
[`Outlet`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html [`Outlet`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html
[`Router`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Router.html [`RouterConfiguration`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/struct.RouterConfiguration.html
[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
[`use_router`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html
[Vue Router]: https://router.vuejs.org/ [Vue Router]: https://router.vuejs.org/

View file

@ -7,12 +7,12 @@ We told them where to go using the `target` property. This property takes a
A [`NavigationTarget`] is similar to the `href` of an HTML anchor element.It A [`NavigationTarget`] is similar to the `href` of an HTML anchor element.It
tells the router where to navigate to. The Dioxus Router knows three kinds of tells the router where to navigate to. The Dioxus Router knows three kinds of
navigation targets: navigation targets:
- [`InternalTarget`]: we already saw that. It's basically an `href`, but cannot - [`Internal`]: we already saw that. It's basically an `href`, but cannot
link to content outside our app. link to content outside our app.
- [`ExternalTarget`]: This works exactly like an HTML anchors `href`. In fact, - [`External`]: This works exactly like an HTML anchors `href`. In fact,
it is just passed through. Don't use this for in-app navigation as it'll it is just passed through. Don't use this for in-app navigation as it'll
trigger a page reload by the browser. trigger a page reload by the browser.
- [`NamedTarget`]: this is the most interesting form of navigation target. We'll look - [`Named`]: this is the most interesting form of navigation target. We'll look
at it in detail in this chapter. at it in detail in this chapter.
## External navigation ## External navigation
@ -27,7 +27,7 @@ If we need a link to an external page we can do it like this:
fn GoToDioxus(cx: Scope) -> Element { fn GoToDioxus(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! {
Link { Link {
target: ExternalTarget(String::from("https://dioxuslabs.com")), target: NavigationTarget::External("https://dioxuslabs.com".into()),
"Explicit ExternalTarget target" "Explicit ExternalTarget target"
} }
Link { Link {
@ -38,8 +38,8 @@ fn GoToDioxus(cx: Scope) -> Element {
} }
``` ```
> Note that we can use a `str`, just like with [`InternalTarget`]s. The router > Note that we can use a `str`, just like with [`Internal`]s. The router will
> will convert a `str` to an [`ExternalTarget`] if the URL is absolute. > convert a `str` to an [`External`] if the URL is absolute.
## Named navigation ## Named navigation
When defining our routes, we can optionally give them unique static names. This When defining our routes, we can optionally give them unique static names. This
@ -50,9 +50,10 @@ named navigation we instead give it a name, and let it figure out the path.
This has several advantages: This has several advantages:
- We don't have to remember absolute paths or care about what the current path - We don't have to remember absolute paths or care about what the current path
is is.
- changing paths later on won't break internal links - Changing paths later on won't break internal links.
- paths can easily be localized without affecting app logic - Paths can easily be localized without affecting app logic.
- The compiler makes sure we don't have typos.
Let's try that now! First, we give our blog post route a name. We can reuse our Let's try that now! First, we give our blog post route a name. We can reuse our
`BlogPost` component as a name. `BlogPost` component as a name.
@ -64,23 +65,28 @@ Let's try that now! First, we give our blog post route a name. We can reuse our
# use dioxus_router::prelude::*; # use dioxus_router::prelude::*;
# fn Blog(cx: Scope) -> Element { unimplemented!() } # fn Blog(cx: Scope) -> Element { unimplemented!() }
# fn BlogList(cx: Scope) -> Element { unimplemented!() } # fn BlogList(cx: Scope) -> Element { unimplemented!() }
# struct PostId;
# fn BlogPost(cx: Scope) -> Element { unimplemented!() } # fn BlogPost(cx: Scope) -> Element { unimplemented!() }
# fn Home(cx: Scope) -> Element { unimplemented!() } # fn Home(cx: Scope) -> Element { unimplemented!() }
# fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
# #
struct BlogPostName;
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
let routes = use_segment(&cx, || { use_router(
Segment::default() cx,
.index(Home as Component) &|| RouterConfiguration::default(),
.fixed( &|| {
"blog", Segment::content(comp(Home))
Route::new(Blog as Component).nested( .fixed("blog", Route::content(comp(Blog)).nested(
Segment::default().index(BlogList as Component).catch_all( Segment::content(comp(BlogList)).catch_all(
// notice the name at the end of the line ParameterRoute::content::<PostId>(comp(BlogPost))
ParameterRoute::new("post_id", BlogPost as Component).name(BlogPost), .name::<BlogPostName>() // this is new
),
),
) )
}); ))
.fallback(comp(PageNotFound))
}
);
// ... // ...
# unimplemented!() # unimplemented!()
@ -94,6 +100,8 @@ Now we can change the targets of the links in our `BlogList` component.
# use dioxus::prelude::*; # use dioxus::prelude::*;
# extern crate dioxus_router; # extern crate dioxus_router;
# use dioxus_router::prelude::*; # use dioxus_router::prelude::*;
# struct PostId;
# struct BlogPostName;
# fn BlogPost(cx: Scope) -> Element { unimplemented!() } # fn BlogPost(cx: Scope) -> Element { unimplemented!() }
# #
fn BlogList(cx: Scope) -> Element { fn BlogList(cx: Scope) -> Element {
@ -101,11 +109,13 @@ fn BlogList(cx: Scope) -> Element {
h2 { "Choose a post" } h2 { "Choose a post" }
ul { ul {
li { Link { li { Link {
target: (BlogPost, [("post_id", String::from("1"))]), target: named::<BlogPostName>().parameter::<PostId>("1"),
"Read the first blog post" "Read the first blog post"
} } } }
li { Link { li { Link {
target: (BlogPost, [("post_id", String::from("2"))], "query"), target: named::<BlogPostName>()
.parameter::<PostId>("1")
.query("query"),
"Read the second blog post" "Read the second blog post"
} } } }
} }
@ -113,7 +123,7 @@ fn BlogList(cx: Scope) -> Element {
} }
``` ```
As you can see, a [`NamedTarget`] requires three fields: As you can see, a [`Named`] requires three fields:
1. the name to navigate to 1. the name to navigate to
2. a `Vec` containing all parameters that need to be inserted into the path 2. a `Vec` containing all parameters that need to be inserted into the path
3. optionally a query string to use. 3. optionally a query string to use.
@ -135,7 +145,7 @@ fn NavBar(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! {
nav { nav {
ul { ul {
li { Link { target: (RootIndex, []), "Home" } } li { Link { target: named::<RootIndex>(), "Home" } }
li { Link { target: "/blog", "Blog" } } li { Link { target: "/blog", "Blog" } }
} }
} }
@ -144,8 +154,8 @@ fn NavBar(cx: Scope) -> Element {
``` ```
[`ExternalTarget`]: https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.ExternalTarget [`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External
[`InternalTarget`]: https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.InternalTarget [`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal
[`NamedTarget`]: https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.NamedTarget [`Named`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Named
[`NavigationTarget`]: https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html [`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html
[`RootIndex`]: https://docs.rs/dioxus-router/latest/dioxus_router/names/struct.RootIndex.html [`RootIndex`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.RootIndex.html

View file

@ -21,25 +21,28 @@ All we need to do is update our route definition in our app component:
# use dioxus_router::prelude::*; # use dioxus_router::prelude::*;
# fn Blog(cx: Scope) -> Element { unimplemented!() } # fn Blog(cx: Scope) -> Element { unimplemented!() }
# fn BlogList(cx: Scope) -> Element { unimplemented!() } # fn BlogList(cx: Scope) -> Element { unimplemented!() }
# struct PostId;
# struct BlogPostName;
# fn BlogPost(cx: Scope) -> Element { unimplemented!() } # fn BlogPost(cx: Scope) -> Element { unimplemented!() }
# fn Home(cx: Scope) -> Element { unimplemented!() } # fn Home(cx: Scope) -> Element { unimplemented!() }
# fn NavBar(cx: Scope) -> Element { unimplemented!() } # fn NavBar(cx: Scope) -> Element { unimplemented!() }
# fn PageNotFound(cx: Scope) -> Element { unimplemented!() } # fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
# fn App(cx: Scope) -> Element { # fn App(cx: Scope) -> Element {
let routes = use_segment(&cx, || { use_router(
Segment::new() cx,
.index(Home as Component) &|| RouterConfiguration::default(),
.fixed( &|| {
"blog", Segment::content(comp(Home))
Route::new(Blog as Component).nested( .fixed("blog", Route::content(comp(Blog)).nested(
Segment::new().index(BlogList as Component).catch_all( Segment::content(comp(BlogList)).catch_all(
ParameterRoute::new("post_id", BlogPost as Component).name(BlogPost) ParameterRoute::content::<PostId>(comp(BlogPost))
), .name::<BlogPostName>()
),
) )
))
.fixed("myblog", "/blog") // this is new .fixed("myblog", "/blog") // this is new
.fallback(PageNotFound as Component) .fallback(comp(PageNotFound))
}); }
);
# unimplemented!() # unimplemented!()
# } # }
``` ```