Make Switch to accept a closure as render function directly (#2659)

* RenderFn can be be directly Fn() now.

* Switch switch to function component.

* Make Link a function component.

* Remove non-exhaustive.

* Add migration guide.

* Update Website Docs.

* Fix CI.

* Pushing to Navigator no longer requires an owned instance.

* Fix CI.

* Fix code size.

* Further optimisation.
This commit is contained in:
Kaede Hoshikawa 2022-05-06 09:56:49 +09:00 committed by GitHub
parent 27da544cae
commit 00b94d0051
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 223 additions and 294 deletions

View File

@ -37,7 +37,7 @@ pub fn App() -> Html {
<Nav />
<main>
<Switch<Route> render={Switch::render(switch)} />
<Switch<Route> render={switch} />
</main>
<footer class="footer">
<div class="content has-text-centered">
@ -71,7 +71,7 @@ pub fn ServerApp(props: &ServerAppProps) -> Html {
<Nav />
<main>
<Switch<Route> render={Switch::render(switch)} />
<Switch<Route> render={switch} />
</main>
<footer class="footer">
<div class="content has-text-centered">
@ -87,8 +87,8 @@ pub fn ServerApp(props: &ServerAppProps) -> Html {
}
}
fn switch(routes: &Route) -> Html {
match routes.clone() {
fn switch(routes: Route) -> Html {
match routes {
Route::Post { id } => {
html! { <Post seed={id} /> }
}

View File

@ -62,7 +62,7 @@ impl Component for App {
{ self.view_nav(ctx.link()) }
<main>
<Switch<Route> render={Switch::render(switch)} />
<Switch<Route> render={switch} />
</main>
<footer class="footer">
<div class="content has-text-centered">
@ -124,8 +124,8 @@ impl App {
}
}
fn switch(routes: &Route) -> Html {
match routes.clone() {
fn switch(routes: Route) -> Html {
match routes {
Route::Post { id } => {
html! { <Post seed={id} /> }
}

View File

@ -1,12 +1,10 @@
use std::marker::PhantomData;
use serde::Serialize;
use wasm_bindgen::UnwrapThrowExt;
use yew::prelude::*;
use yew::virtual_dom::AttrValue;
use crate::navigator::NavigatorKind;
use crate::scope_ext::RouterScopeExt;
use crate::prelude::*;
use crate::{utils, Routable};
/// Props for [`Link`]
@ -31,96 +29,65 @@ where
}
/// A wrapper around `<a>` tag to be used with [`Router`](crate::Router)
pub struct Link<R, Q = ()>
#[function_component]
pub fn Link<R, Q = ()>(props: &LinkProps<R, Q>) -> Html
where
R: Routable + 'static,
Q: Clone + PartialEq + Serialize + 'static,
{
_route: PhantomData<R>,
_query: PhantomData<Q>,
}
let LinkProps {
classes,
to,
children,
disabled,
query,
} = props.clone();
pub enum Msg {
OnClick,
}
let navigator = use_navigator().expect_throw("failed to get navigator");
impl<R, Q> Component for Link<R, Q>
where
R: Routable + 'static,
Q: Clone + PartialEq + Serialize + 'static,
{
type Message = Msg;
type Properties = LinkProps<R, Q>;
let onclick = {
let navigator = navigator.clone();
let to = to.clone();
let query = query.clone();
fn create(_ctx: &Context<Self>) -> Self {
Self {
_route: PhantomData,
_query: PhantomData,
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::OnClick => {
let LinkProps { to, query, .. } = ctx.props();
let navigator = ctx
.link()
.navigator()
.expect_throw("failed to get navigator");
match query {
None => {
navigator.push(to.clone());
}
Some(data) => {
navigator
.push_with_query(to.clone(), data.clone())
.expect_throw("failed push history with query");
}
};
false
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let LinkProps {
classes,
to,
query,
children,
disabled,
..
} = ctx.props().clone();
let onclick = ctx.link().callback(|e: MouseEvent| {
Callback::from(move |e: MouseEvent| {
e.prevent_default();
Msg::OnClick
});
let navigator = ctx
.link()
.navigator()
.expect_throw("failed to get navigator");
let href: AttrValue = {
let pathname = navigator.route_to_url(to);
let path = query
.and_then(|query| serde_urlencoded::to_string(query).ok())
.and_then(|query| utils::compose_path(&pathname, &query))
.unwrap_or_else(|| pathname.to_string());
match navigator.kind() {
NavigatorKind::Hash => format!("#{}", path),
_ => path,
match query {
None => {
navigator.push(&to);
}
Some(ref data) => {
navigator
.push_with_query(&to, data)
.expect_throw("failed push history with query");
}
}
.into()
};
html! {
<a class={classes}
{href}
{onclick}
{disabled}
>
{ children }
</a>
})
};
let href = {
let route_s = to.to_path();
let pathname = navigator.prefix_basename(&route_s);
let mut path = query
.and_then(|query| serde_urlencoded::to_string(query).ok())
.and_then(|query| utils::compose_path(&pathname, &query))
.unwrap_or_else(|| pathname.into_owned());
if navigator.kind() == NavigatorKind::Hash {
path.insert(0, '#');
}
AttrValue::from(path)
};
html! {
<a class={classes}
{href}
{onclick}
{disabled}
>
{ children }
</a>
}
}

View File

@ -21,7 +21,7 @@ where
let target_route = props.to.clone();
use_effect(move || {
history.push(target_route.clone());
history.push(&target_route);
|| {}
});

View File

@ -23,7 +23,7 @@
//! fn secure() -> Html {
//! let navigator = use_navigator().unwrap();
//!
//! let onclick_callback = Callback::from(move |_| navigator.push(Route::Home));
//! let onclick_callback = Callback::from(move |_| navigator.push(&Route::Home));
//! html! {
//! <div>
//! <h1>{ "Secure" }</h1>
@ -36,12 +36,12 @@
//! fn app() -> Html {
//! html! {
//! <BrowserRouter>
//! <Switch<Route> render={Switch::render(switch)} />
//! <Switch<Route> render={switch} />
//! </BrowserRouter>
//! }
//! }
//!
//! fn switch(routes: &Route) -> Html {
//! fn switch(routes: Route) -> Html {
//! match routes {
//! Route::Home => html! { <h1>{ "Home" }</h1> },
//! Route::Secure => html! {
@ -79,7 +79,7 @@ pub mod utils;
pub use routable::{AnyRoute, Routable};
pub use router::{BrowserRouter, HashRouter, Router};
pub use switch::{RenderFn, Switch};
pub use switch::Switch;
pub mod history {
//! A module that provides universal session history and location information.

View File

@ -57,77 +57,94 @@ impl Navigator {
}
/// Pushes a [`Routable`] entry.
pub fn push(&self, route: impl Routable) {
self.inner.push(self.route_to_url(route));
pub fn push<R>(&self, route: &R)
where
R: Routable,
{
self.inner.push(self.prefix_basename(&route.to_path()));
}
/// Replaces the current history entry with provided [`Routable`] and [`None`] state.
pub fn replace(&self, route: impl Routable) {
self.inner.replace(self.route_to_url(route));
pub fn replace<R>(&self, route: &R)
where
R: Routable,
{
self.inner.replace(self.prefix_basename(&route.to_path()));
}
/// Pushes a [`Routable`] entry with state.
pub fn push_with_state<T>(&self, route: impl Routable, state: T)
pub fn push_with_state<R, T>(&self, route: &R, state: T)
where
R: Routable,
T: 'static,
{
self.inner.push_with_state(self.route_to_url(route), state);
self.inner
.push_with_state(self.prefix_basename(&route.to_path()), state);
}
/// Replaces the current history entry with provided [`Routable`] and state.
pub fn replace_with_state<T>(&self, route: impl Routable, state: T)
pub fn replace_with_state<R, T>(&self, route: &R, state: T)
where
R: Routable,
T: 'static,
{
self.inner
.replace_with_state(self.route_to_url(route), state);
.replace_with_state(self.prefix_basename(&route.to_path()), state);
}
/// Same as `.push()` but affix the queries to the end of the route.
pub fn push_with_query<Q>(&self, route: impl Routable, query: Q) -> NavigationResult<()>
pub fn push_with_query<R, Q>(&self, route: &R, query: &Q) -> NavigationResult<()>
where
R: Routable,
Q: Serialize,
{
self.inner.push_with_query(self.route_to_url(route), query)
self.inner
.push_with_query(self.prefix_basename(&route.to_path()), query)
}
/// Same as `.replace()` but affix the queries to the end of the route.
pub fn replace_with_query<Q>(&self, route: impl Routable, query: Q) -> NavigationResult<()>
pub fn replace_with_query<R, Q>(&self, route: &R, query: &Q) -> NavigationResult<()>
where
R: Routable,
Q: Serialize,
{
self.inner
.replace_with_query(self.route_to_url(route), query)
.replace_with_query(self.prefix_basename(&route.to_path()), query)
}
/// Same as `.push_with_state()` but affix the queries to the end of the route.
pub fn push_with_query_and_state<Q, T>(
pub fn push_with_query_and_state<R, Q, T>(
&self,
route: impl Routable,
query: Q,
route: &R,
query: &Q,
state: T,
) -> NavigationResult<()>
where
R: Routable,
Q: Serialize,
T: 'static,
{
self.inner
.push_with_query_and_state(self.route_to_url(route), query, state)
.push_with_query_and_state(self.prefix_basename(&route.to_path()), query, state)
}
/// Same as `.replace_with_state()` but affix the queries to the end of the route.
pub fn replace_with_query_and_state<Q, T>(
pub fn replace_with_query_and_state<R, Q, T>(
&self,
route: impl Routable,
query: Q,
route: &R,
query: &Q,
state: T,
) -> NavigationResult<()>
where
R: Routable,
Q: Serialize,
T: 'static,
{
self.inner
.replace_with_query_and_state(self.route_to_url(route), query, state)
self.inner.replace_with_query_and_state(
self.prefix_basename(&route.to_path()),
query,
state,
)
}
/// Returns the Navigator kind.
@ -139,22 +156,17 @@ impl Navigator {
}
}
pub(crate) fn route_to_url(&self, route: impl Routable) -> Cow<'static, str> {
let url = route.to_path();
let path = match self.basename() {
pub(crate) fn prefix_basename<'a>(&self, route_s: &'a str) -> Cow<'a, str> {
match self.basename() {
Some(base) => {
let path = format!("{}{}", base, url);
if path.is_empty() {
if route_s.is_empty() && route_s.is_empty() {
Cow::from("/")
} else {
path.into()
Cow::from(format!("{}{}", base, route_s))
}
}
None => url.into(),
};
path
None => route_s.into(),
}
}
pub(crate) fn strip_basename<'a>(&self, path: Cow<'a, str>) -> Cow<'a, str> {

View File

@ -30,12 +30,16 @@ pub struct NavigatorHandle {
/// # use wasm_bindgen::UnwrapThrowExt;
/// # use yew::prelude::*;
/// # use yew_router::prelude::*;
/// # use yew_router::components::{LinkProps, Msg};
/// # use yew_router::components::LinkProps;
/// #
/// # pub struct Link<R: Routable + 'static> {
/// # _data: PhantomData<R>,
/// # }
/// #
/// # pub enum Msg {
/// # OnClick,
/// # }
/// #
/// impl<R: Routable + 'static> Component for Link<R> {
/// type Message = Msg;
/// type Properties = LinkProps<R>;
@ -50,7 +54,7 @@ pub struct NavigatorHandle {
/// ctx.link()
/// .navigator()
/// .expect_throw("failed to get navigator.")
/// .push(ctx.props().to.clone());
/// .push(&ctx.props().to);
/// false
/// }
/// }

View File

@ -1,44 +1,9 @@
//! The [`Switch`] Component.
use std::marker::PhantomData;
use std::rc::Rc;
use gloo::console;
use wasm_bindgen::UnwrapThrowExt;
use yew::prelude::*;
use crate::prelude::*;
use crate::scope_ext::LocationHandle;
/// Wraps `Rc` around `Fn` so it can be passed as a prop.
pub struct RenderFn<R>(Rc<dyn Fn(&R) -> Html>);
impl<R> RenderFn<R> {
/// Creates a new [`RenderFn`]
///
/// It is recommended that you use [`Switch::render`] instead
pub fn new(value: impl Fn(&R) -> Html + 'static) -> Self {
Self(Rc::new(value))
}
pub fn render(&self, route: &R) -> Html {
(self.0)(route)
}
}
impl<T> Clone for RenderFn<T> {
fn clone(&self) -> Self {
Self(Rc::clone(&self.0))
}
}
impl<T> PartialEq for RenderFn<T> {
fn eq(&self, other: &Self) -> bool {
// https://github.com/rust-lang/rust-clippy/issues/6524
#[allow(clippy::vtable_address_comparisons)]
Rc::ptr_eq(&self.0, &other.0)
}
}
/// Props for [`Switch`]
#[derive(Properties, PartialEq, Clone)]
@ -47,16 +12,11 @@ where
R: Routable,
{
/// Callback which returns [`Html`] to be rendered for the current route.
pub render: RenderFn<R>,
pub render: Callback<R, Html>,
#[prop_or_default]
pub pathname: Option<String>,
}
#[doc(hidden)]
pub enum Msg {
ReRender,
}
/// A Switch that dispatches route among variants of a [`Routable`].
///
/// When a route can't be matched, including when the path is matched but the deserialization fails,
@ -65,65 +25,24 @@ pub enum Msg {
/// Otherwise `html! {}` is rendered and a message is logged to console
/// stating that no route can be matched.
/// See the [crate level document][crate] for more information.
pub struct Switch<R: Routable + 'static> {
_listener: LocationHandle,
_phantom: PhantomData<R>,
}
impl<R> Component for Switch<R>
#[function_component]
pub fn Switch<R>(props: &SwitchProps<R>) -> Html
where
R: Routable + 'static,
{
type Message = Msg;
type Properties = SwitchProps<R>;
let route = use_route::<R>();
fn create(ctx: &Context<Self>) -> Self {
let link = ctx.link();
let listener = link
.add_location_listener(link.callback(move |_| Msg::ReRender))
.expect_throw("failed to create history handle. Do you have a router registered?");
let route = props
.pathname
.as_ref()
.and_then(|p| R::recognize(p))
.or(route);
Self {
_listener: listener,
_phantom: PhantomData,
match route {
Some(route) => props.render.emit(route),
None => {
console::warn!("no route matched");
Html::default()
}
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ReRender => true,
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let route = ctx
.props()
.pathname
.as_ref()
.and_then(|p| R::recognize(p))
.or_else(|| ctx.link().route::<R>());
let children = match &route {
Some(ref route) => (ctx.props().render.0)(route),
None => {
console::warn!("no route matched");
Html::default()
}
};
html! {<>{children}</>}
}
}
impl<R> Switch<R>
where
R: Routable + Clone + 'static,
{
/// Creates a [`RenderFn`].
pub fn render<F>(func: F) -> RenderFn<R>
where
F: Fn(&R) -> Html + 'static,
{
RenderFn::new(func)
}
}

View File

@ -50,13 +50,13 @@ fn no(props: &NoProps) -> Html {
fn component() -> Html {
let navigator = use_navigator().unwrap();
let switch = Switch::render(move |routes| {
let switch = move |routes| {
let navigator_clone = navigator.clone();
let replace_route = Callback::from(move |_| {
navigator_clone
.replace_with_query(
Routes::No { id: 2 },
Query {
&Routes::No { id: 2 },
&Query {
foo: "bar".to_string(),
},
)
@ -67,8 +67,8 @@ fn component() -> Html {
let push_route = Callback::from(move |_| {
navigator_clone
.push_with_query(
Routes::No { id: 3 },
Query {
&Routes::No { id: 3 },
&Query {
foo: "baz".to_string(),
},
)
@ -84,13 +84,13 @@ fn component() -> Html {
},
Routes::No { id } => html! {
<>
<No id={*id} />
<No id={id} />
<button onclick={push_route}>{"push a route"}</button>
</>
},
Routes::NotFound => html! { <div id="result">{"404"}</div> },
}
});
};
html! {
<Switch<Routes> render={switch} />

View File

@ -50,13 +50,13 @@ fn no(props: &NoProps) -> Html {
fn component() -> Html {
let navigator = use_navigator().unwrap();
let switch = Switch::render(move |routes| {
let switch = move |routes| {
let navigator_clone = navigator.clone();
let replace_route = Callback::from(move |_| {
navigator_clone
.replace_with_query(
Routes::No { id: 2 },
Query {
&Routes::No { id: 2 },
&Query {
foo: "bar".to_string(),
},
)
@ -67,8 +67,8 @@ fn component() -> Html {
let push_route = Callback::from(move |_| {
navigator_clone
.push_with_query(
Routes::No { id: 3 },
Query {
&Routes::No { id: 3 },
&Query {
foo: "baz".to_string(),
},
)
@ -84,13 +84,13 @@ fn component() -> Html {
},
Routes::No { id } => html! {
<>
<No id={*id} />
<No id={id} />
<button onclick={push_route}>{"push a route"}</button>
</>
},
Routes::NotFound => html! { <div id="result">{"404"}</div> },
}
});
};
html! {
<Switch<Routes> render={switch} />

View File

@ -50,13 +50,13 @@ fn no(props: &NoProps) -> Html {
fn component() -> Html {
let navigator = use_navigator().unwrap();
let switch = Switch::render(move |routes| {
let switch = move |routes| {
let navigator_clone = navigator.clone();
let replace_route = Callback::from(move |_| {
navigator_clone
.replace_with_query(
Routes::No { id: 2 },
Query {
&Routes::No { id: 2 },
&Query {
foo: "bar".to_string(),
},
)
@ -67,8 +67,8 @@ fn component() -> Html {
let push_route = Callback::from(move |_| {
navigator_clone
.push_with_query(
Routes::No { id: 3 },
Query {
&Routes::No { id: 3 },
&Query {
foo: "baz".to_string(),
},
)
@ -84,13 +84,13 @@ fn component() -> Html {
},
Routes::No { id } => html! {
<>
<No id={*id} />
<No id={id} />
<button onclick={push_route}>{"push a route"}</button>
</>
},
Routes::NotFound => html! { <div id="result">{"404"}</div> },
}
});
};
html! {
<Switch<Routes> render={switch} />

View File

@ -16,7 +16,7 @@ pub use use_reducer::*;
pub use use_ref::*;
pub use use_state::*;
use crate::functional::{AnyScope, HookContext};
use crate::functional::HookContext;
/// A trait that is implemented on hooks.
///
@ -52,17 +52,3 @@ impl<T> Hook for BoxedHook<'_, T> {
(self.inner)(ctx)
}
}
pub(crate) fn use_component_scope() -> impl Hook<Output = AnyScope> {
struct HookProvider {}
impl Hook for HookProvider {
type Output = AnyScope;
fn run(self, ctx: &mut HookContext) -> Self::Output {
ctx.scope.clone()
}
}
HookProvider {}
}

View File

@ -1,6 +1,10 @@
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use crate::callback::Callback;
use crate::context::ContextHandle;
use crate::functional::{hook, use_component_scope, use_memo, use_state};
use crate::functional::{Hook, HookContext};
/// Hook for consuming context values in function components.
/// The context of the type passed as `T` is returned. If there is no such context in scope, `None`
@ -65,29 +69,53 @@ use crate::functional::{hook, use_component_scope, use_memo, use_state};
/// }
/// }
/// ```
#[hook]
pub fn use_context<T: Clone + PartialEq + 'static>() -> Option<T> {
struct UseContext<T: Clone + PartialEq + 'static> {
context: Option<(T, ContextHandle<T>)>,
pub fn use_context<T: Clone + PartialEq + 'static>() -> impl Hook<Output = Option<T>> {
struct HookProvider<T: Clone + PartialEq + 'static> {
_marker: PhantomData<T>,
}
let scope = use_component_scope();
struct UseContext<T: Clone + PartialEq + 'static> {
_handle: Option<ContextHandle<T>>,
value: Rc<RefCell<Option<T>>>,
}
let val = use_state(|| -> Option<T> { None });
let state = {
let val_dispatcher = val.setter();
use_memo(
move |_| UseContext {
context: scope.context::<T>(Callback::from(move |m| {
val_dispatcher.clone().set(Some(m));
})),
},
(),
)
};
impl<T> Hook for HookProvider<T>
where
T: Clone + PartialEq + 'static,
{
type Output = Option<T>;
// we fallback to initial value if it was not updated.
(*val)
.clone()
.or_else(move || state.context.as_ref().map(|m| m.0.clone()))
fn run(self, ctx: &mut HookContext) -> Self::Output {
let scope = ctx.scope.clone();
let state = ctx.next_state(move |re_render| -> UseContext<T> {
let value_cell: Rc<RefCell<Option<T>>> = Rc::default();
let (init_value, handle) = {
let value_cell = value_cell.clone();
scope.context(Callback::from(move |m| {
*(value_cell.borrow_mut()) = Some(m);
re_render()
}))
}
.map(|(value, handle)| (Some(value), Some(handle)))
.unwrap_or((None, None));
*(value_cell.borrow_mut()) = init_value;
UseContext {
_handle: handle,
value: value_cell,
}
});
let value = state.value.borrow();
value.clone()
}
}
HookProvider {
_marker: PhantomData,
}
}

View File

@ -1,4 +1,5 @@
#![cfg(feature = "hydration")]
#![cfg(target_arch = "wasm32")]
use std::rc::Rc;
use std::time::Duration;

View File

@ -71,7 +71,7 @@ enum Route {
fn secure() -> Html {
let navigator = use_navigator().unwrap();
let onclick = Callback::from(move |_| navigator.push(Route::Home));
let onclick = Callback::from(move |_| navigator.push(&Route::Home));
html! {
<div>
<h1>{ "Secure" }</h1>
@ -80,7 +80,7 @@ fn secure() -> Html {
}
}
fn switch(routes: &Route) -> Html {
fn switch(routes: Route) -> Html {
match routes {
Route::Home => html! { <h1>{ "Home" }</h1> },
Route::Secure => html! {
@ -94,7 +94,7 @@ fn switch(routes: &Route) -> Html {
fn app() -> Html {
html! {
<BrowserRouter>
<Switch<Route> render={Switch::render(switch)} />
<Switch<Route> render={switch} />
</BrowserRouter>
}
}
@ -206,7 +206,7 @@ Here's how to implement a button that navigates to the `Home` route when clicked
#[function_component(MyComponent)]
pub fn my_component() -> Html {
let navigator = use_navigator().unwrap();
let onclick = Callback::from(move |_| navigator.push(Route::Home));
let onclick = Callback::from(move |_| navigator.push(&Route::Home));
html! {
<>
@ -239,7 +239,7 @@ pub fn nav_items() -> Html {
let go_home_button = {
let navigator = navigator.clone();
let onclick = Callback::from(move |_| navigator.push(Route::Home));
let onclick = Callback::from(move |_| navigator.push(&Route::Home));
html! {
<button {onclick}>{"click to go home"}</button>
}
@ -247,14 +247,14 @@ pub fn nav_items() -> Html {
let go_to_first_post_button = {
let navigator = navigator.clone();
let onclick = Callback::from(move |_| navigator.push(Route::Post { id: "first-post".to_string() }));
let onclick = Callback::from(move |_| navigator.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::from(move |_| navigator.push(Route::Secure));
let onclick = Callback::from(move |_| navigator.push(&Route::Secure));
html! {
<button {onclick}>{"click to go to secure"}</button>
}
@ -278,7 +278,7 @@ identical with the function component case. Here's an example of a view function
```rust ,ignore
fn view(&self, ctx: &Context<Self>) -> Html {
let navigator = ctx.link().navigator().unwrap();
let onclick = Callback::from(move |_| navigator.push(MainRoute::Home));
let onclick = Callback::from(move |_| navigator.push(&MainRoute::Home));
html!{
<button {onclick}>{"Go Home"}</button>
}
@ -455,17 +455,17 @@ enum SettingsRoute {
NotFound,
}
fn switch_main(route: &MainRoute) -> Html {
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::SettingsRoot | MainRoute::Settings => html! { <Switch<SettingsRoute> render={Switch::render(switch_settings)} /> },
MainRoute::SettingsRoot | MainRoute::Settings => html! { <Switch<SettingsRoute> render={switch_settings} /> },
MainRoute::NotFound => html! {<h1>{"Not Found"}</h1>},
}
}
fn switch_settings(route: &SettingsRoute) -> Html {
fn switch_settings(route: SettingsRoute) -> Html {
match route {
SettingsRoute::Profile => html! {<h1>{"Profile"}</h1>},
SettingsRoute::Friends => html! {<h1>{"Friends"}</h1>},
@ -478,7 +478,7 @@ fn switch_settings(route: &SettingsRoute) -> Html {
pub fn app() -> Html {
html! {
<BrowserRouter>
<Switch<MainRoute> render={Switch::render(switch_main)} />
<Switch<MainRoute> render={switch_main} />
</BrowserRouter>
}
}

View File

@ -0,0 +1,12 @@
---
title: 'From 0.16.0 to 0.17.0'
---
## `Switch::render` is no longer needed
The `<Switch />` component now accepts a closure of `Fn(Routable) -> Html` as
the render function directly.
## `navigator` API
The History API has been replaced with the Navigator API.