mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
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:
parent
27da544cae
commit
00b94d0051
@ -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} /> }
|
||||
}
|
||||
|
||||
@ -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} /> }
|
||||
}
|
||||
|
||||
@ -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,88 +29,58 @@ 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>,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
OnClick,
|
||||
}
|
||||
|
||||
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>;
|
||||
|
||||
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| {
|
||||
e.prevent_default();
|
||||
Msg::OnClick
|
||||
});
|
||||
query,
|
||||
} = props.clone();
|
||||
|
||||
let navigator = ctx
|
||||
.link()
|
||||
.navigator()
|
||||
.expect_throw("failed to get navigator");
|
||||
let href: AttrValue = {
|
||||
let pathname = navigator.route_to_url(to);
|
||||
let path = query
|
||||
let navigator = use_navigator().expect_throw("failed to get navigator");
|
||||
|
||||
let onclick = {
|
||||
let navigator = navigator.clone();
|
||||
let to = to.clone();
|
||||
let query = query.clone();
|
||||
|
||||
Callback::from(move |e: MouseEvent| {
|
||||
e.prevent_default();
|
||||
|
||||
match query {
|
||||
None => {
|
||||
navigator.push(&to);
|
||||
}
|
||||
Some(ref data) => {
|
||||
navigator
|
||||
.push_with_query(&to, data)
|
||||
.expect_throw("failed push history with query");
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
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.to_string());
|
||||
.unwrap_or_else(|| pathname.into_owned());
|
||||
|
||||
match navigator.kind() {
|
||||
NavigatorKind::Hash => format!("#{}", path),
|
||||
_ => path,
|
||||
if navigator.kind() == NavigatorKind::Hash {
|
||||
path.insert(0, '#');
|
||||
}
|
||||
.into()
|
||||
|
||||
AttrValue::from(path)
|
||||
};
|
||||
|
||||
html! {
|
||||
<a class={classes}
|
||||
{href}
|
||||
@ -122,5 +90,4 @@ where
|
||||
{ children }
|
||||
</a>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ where
|
||||
|
||||
let target_route = props.to.clone();
|
||||
use_effect(move || {
|
||||
history.push(target_route.clone());
|
||||
history.push(&target_route);
|
||||
|
||||
|| {}
|
||||
});
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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
|
||||
/// }
|
||||
/// }
|
||||
|
||||
@ -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?");
|
||||
|
||||
Self {
|
||||
_listener: listener,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
let route = props
|
||||
.pathname
|
||||
.as_ref()
|
||||
.and_then(|p| R::recognize(p))
|
||||
.or_else(|| ctx.link().route::<R>());
|
||||
.or(route);
|
||||
|
||||
let children = match &route {
|
||||
Some(ref route) => (ctx.props().render.0)(route),
|
||||
match route {
|
||||
Some(route) => props.render.emit(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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -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 {}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#![cfg(feature = "hydration")]
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
Loading…
x
Reference in New Issue
Block a user