diff --git a/examples/function_todomvc/src/components/entry.rs b/examples/function_todomvc/src/components/entry.rs index 79104a6af4d..d55c477f15f 100644 --- a/examples/function_todomvc/src/components/entry.rs +++ b/examples/function_todomvc/src/components/entry.rs @@ -37,6 +37,16 @@ pub fn entry(props: &EntryProps) -> Html { class.push("completed"); } + let ontoggle = { + let ontoggle = props.ontoggle.clone(); + move |_| ontoggle.emit(id) + }; + + let onremove = { + let onremove = props.onremove.clone(); + move |_| onremove.emit(id) + }; + html! {
  • @@ -44,14 +54,12 @@ pub fn entry(props: &EntryProps) -> Html { type="checkbox" class="toggle" checked={props.entry.completed} - onclick={props.ontoggle.reform(move |_| id)} + onclick={ontoggle} /> -
  • diff --git a/examples/function_todomvc/src/components/filter.rs b/examples/function_todomvc/src/components/filter.rs index 4380d063cdf..f295aed06de 100644 --- a/examples/function_todomvc/src/components/filter.rs +++ b/examples/function_todomvc/src/components/filter.rs @@ -18,11 +18,16 @@ pub fn Filter(props: &FilterProps) -> Html { "not-selected" }; + let onset_filter = { + let onset_filter = props.onset_filter.clone(); + move |_| onset_filter.emit(filter) + }; + html! {
  • { props.filter } diff --git a/examples/function_todomvc/src/hooks/use_bool_toggle.rs b/examples/function_todomvc/src/hooks/use_bool_toggle.rs index 1ca6ae55b70..221fe27f363 100644 --- a/examples/function_todomvc/src/hooks/use_bool_toggle.rs +++ b/examples/function_todomvc/src/hooks/use_bool_toggle.rs @@ -1,9 +1,10 @@ use std::ops::Deref; use std::rc::Rc; -use yew::functional::use_hook; +use yew::{use_state_eq, UseStateHandle}; +#[derive(Clone)] pub struct UseBoolToggleHandle { - value: bool, + value: UseStateHandle, toggle: Rc, } @@ -34,37 +35,28 @@ impl Deref for UseBoolToggleHandle { /// ... /// let value = use_bool_toggle(false); /// ... -/// /// ... /// ``` pub fn use_bool_toggle(default: bool) -> UseBoolToggleHandle { - use_hook( - || default, - move |hook, updater| { - updater.post_render(move |state: &mut bool| { - if *state != default { - *state = default; - } - false - }); + let state = use_state_eq(|| default); - let toggle = Rc::new(move || { - updater.callback(move |st: &mut bool| { - *st = !*st; - true - }) - }); + let toggle = { + let state = state.clone(); + Rc::new(move || state.set(!*state)) + }; - UseBoolToggleHandle { - value: *hook, - toggle, - } - }, - |_| {}, - ) + UseBoolToggleHandle { + value: state, + toggle, + } } diff --git a/packages/yew-agent/src/link.rs b/packages/yew-agent/src/link.rs index d2076f0d5bb..9d7ef52f10c 100644 --- a/packages/yew-agent/src/link.rs +++ b/packages/yew-agent/src/link.rs @@ -91,28 +91,6 @@ impl AgentLink { closure.into() } - /// This method creates a [`Callback`] from [`FnOnce`] which returns a Future - /// which returns a message to be sent back to the agent. - /// - /// # Panics - /// If the future panics, then the promise will not resolve, and - /// will leak. - pub fn callback_future_once(&self, function: FN) -> Callback - where - M: Into, - FU: Future + 'static, - FN: FnOnce(IN) -> FU + 'static, - { - let link = self.clone(); - - let closure = move |input: IN| { - let future: FU = function(input); - link.send_future(future); - }; - - Callback::once(closure) - } - /// This method processes a Future that returns a message and sends it back to the agent. /// /// # Panics diff --git a/packages/yew/src/callback.rs b/packages/yew/src/callback.rs index 58fba1953a3..d904f378360 100644 --- a/packages/yew/src/callback.rs +++ b/packages/yew/src/callback.rs @@ -5,7 +5,6 @@ //! - [Timer](https://github.com/yewstack/yew/tree/master/examples/timer) use crate::html::ImplicitClone; -use std::cell::RefCell; use std::fmt; use std::rc::Rc; @@ -16,104 +15,51 @@ use std::rc::Rc; /// Callbacks should be used from JS callbacks or `setTimeout` calls. /// /// An `Rc` wrapper is used to make it cloneable. -pub enum Callback { - /// A callback which can be called multiple times with optional modifier flags - Callback { - /// A callback which can be called multiple times - cb: Rc, - - /// Setting `passive` to [Some] explicitly makes the event listener passive or not. - /// Yew sets sane defaults depending on the type of the listener. - /// See - /// [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener). - passive: Option, - }, - - /// A callback which can only be called once. The callback will panic if it is - /// called more than once. - CallbackOnce(Rc>), +pub struct Callback { + /// A callback which can be called multiple times + pub(crate) cb: Rc OUT>, } -type CallbackOnce = RefCell>>; - -impl From for Callback { +impl OUT + 'static> From for Callback { fn from(func: F) -> Self { - Callback::Callback { - cb: Rc::new(func), - passive: None, - } + Callback { cb: Rc::new(func) } } } -impl Clone for Callback { +impl Clone for Callback { fn clone(&self) -> Self { - match self { - Callback::Callback { cb, passive } => Callback::Callback { - cb: cb.clone(), - passive: *passive, - }, - Callback::CallbackOnce(cb) => Callback::CallbackOnce(cb.clone()), + Self { + cb: self.cb.clone(), } } } #[allow(clippy::vtable_address_comparisons)] -impl PartialEq for Callback { - fn eq(&self, other: &Callback) -> bool { - match (&self, &other) { - (Callback::CallbackOnce(cb), Callback::CallbackOnce(other_cb)) => { - Rc::ptr_eq(cb, other_cb) - } - ( - Callback::Callback { cb, passive }, - Callback::Callback { - cb: rhs_cb, - passive: rhs_passive, - }, - ) => Rc::ptr_eq(cb, rhs_cb) && passive == rhs_passive, - _ => false, - } +impl PartialEq for Callback { + fn eq(&self, other: &Callback) -> bool { + let (Callback { cb }, Callback { cb: rhs_cb }) = (self, other); + Rc::ptr_eq(cb, rhs_cb) } } -impl fmt::Debug for Callback { +impl fmt::Debug for Callback { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let data = match self { - Callback::Callback { .. } => "Callback<_>", - Callback::CallbackOnce(_) => "CallbackOnce<_>", - }; - - f.write_str(data) + write!(f, "Callback<_>") } } -impl Callback { +impl Callback { /// This method calls the callback's function. - pub fn emit(&self, value: IN) { - match self { - Callback::Callback { cb, .. } => cb(value), - Callback::CallbackOnce(rc) => { - let cb = rc.replace(None); - let f = cb.expect("callback contains `FnOnce` which has already been used"); - f(value) - } - }; - } - - /// Creates a callback from an `FnOnce`. The programmer is responsible for ensuring - /// that the callback is only called once. If it is called more than once, the callback - /// will panic. - pub fn once(func: F) -> Self - where - F: FnOnce(IN) + 'static, - { - Callback::CallbackOnce(Rc::new(RefCell::new(Some(Box::new(func))))) + pub fn emit(&self, value: IN) -> OUT { + (*self.cb)(value) } +} +impl Callback { /// Creates a "no-op" callback which can be used when it is not suitable to use an /// `Option`. pub fn noop() -> Self { - Self::from(|_| {}) + Self::from(|_| ()) } } @@ -123,9 +69,9 @@ impl Default for Callback { } } -impl Callback { - /// Changes the input type of the callback to another. - /// Works like the `map` method but in the opposite direction. +impl Callback { + /// Creates a new callback from another callback and a function + /// That when emited will call that function and will emit the original callback pub fn reform(&self, func: F) -> Callback where F: Fn(T) -> IN + 'static, @@ -139,4 +85,4 @@ impl Callback { } } -impl ImplicitClone for Callback {} +impl ImplicitClone for Callback {} diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index ef88d11ad1d..484f3918eb9 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -301,29 +301,6 @@ impl Scope { /// synchronously schedules a call to the [Component](Component) /// interface. pub fn callback(&self, function: F) -> Callback - where - M: Into, - F: Fn(IN) -> M + 'static, - { - self.callback_with_passive(None, function) - } - - /// Creates a `Callback` which will send a message to the linked - /// component's update method when invoked. - /// - /// Setting `passive` to [Some] explicitly makes the event listener passive or not. - /// Yew sets sane defaults depending on the type of the listener. - /// See - /// [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener). - /// - /// Please be aware that currently the result of this callback - /// synchronously schedules a call to the [Component](Component) - /// interface. - pub fn callback_with_passive( - &self, - passive: impl Into>, - function: F, - ) -> Callback where M: Into, F: Fn(IN) -> M + 'static, @@ -333,29 +310,7 @@ impl Scope { let output = function(input); scope.send_message(output); }; - Callback::Callback { - passive: passive.into(), - cb: Rc::new(closure), - } - } - - /// Creates a `Callback` from an `FnOnce` which will send a message - /// to the linked component's update method when invoked. - /// - /// Please be aware that currently the result of this callback - /// will synchronously schedule calls to the - /// [Component](Component) interface. - pub fn callback_once(&self, function: F) -> Callback - where - M: Into, - F: FnOnce(IN) -> M + 'static, - { - let scope = self.clone(); - let closure = move |input| { - let output = function(input); - scope.send_message(output); - }; - Callback::once(closure) + Callback::from(closure) } /// Creates a `Callback` which will send a batch of messages back @@ -386,34 +341,6 @@ impl Scope { closure.into() } - /// Creates a `Callback` from an `FnOnce` which will send a batch of messages back - /// to the linked component's update method when invoked. - /// - /// The callback function's return type is generic to allow for dealing with both - /// `Option` and `Vec` nicely. `Option` can be used when dealing with a callback that - /// might not need to send an update. - /// - /// ```ignore - /// link.batch_callback_once(|_| vec![Msg::A, Msg::B]); - /// link.batch_callback_once(|_| Some(Msg::A)); - /// ``` - /// - /// Please be aware that currently the results of these callbacks - /// will synchronously schedule calls to the - /// [Component](Component) interface. - pub fn batch_callback_once(&self, function: F) -> Callback - where - F: FnOnce(IN) -> OUT + 'static, - OUT: SendAsMessage, - { - let scope = self.clone(); - let closure = move |input| { - let messages = function(input); - messages.send(&scope); - }; - Callback::once(closure) - } - /// This method creates a [`Callback`] which returns a Future which /// returns a message to be sent back to the component's event /// loop. @@ -437,29 +364,6 @@ impl Scope { closure.into() } - /// This method creates a [`Callback`] from [`FnOnce`] which returns a Future - /// which returns a message to be sent back to the component's event - /// loop. - /// - /// # Panics - /// If the future panics, then the promise will not resolve, and - /// will leak. - pub fn callback_future_once(&self, function: FN) -> Callback - where - M: Into, - FU: Future + 'static, - FN: FnOnce(IN) -> FU + 'static, - { - let link = self.clone(); - - let closure = move |input: IN| { - let future: FU = function(input); - link.send_future(future); - }; - - Callback::once(closure) - } - /// This method processes a Future that returns a message and sends it back to the component's /// loop. /// diff --git a/packages/yew/src/html/listener/events.rs b/packages/yew/src/html/listener/events.rs index db11fd8d1c9..a1b8677f6d6 100644 --- a/packages/yew/src/html/listener/events.rs +++ b/packages/yew/src/html/listener/events.rs @@ -47,10 +47,7 @@ macro_rules! impl_action { } fn passive(&self) -> bool { - match &self.callback { - Callback::Callback{passive, ..} => (*passive).unwrap_or($passive), - _ => $passive, - } + $passive } } } diff --git a/packages/yew/src/virtual_dom/listeners.rs b/packages/yew/src/virtual_dom/listeners.rs index f6c48e5e30d..4dcd09dca37 100644 --- a/packages/yew/src/virtual_dom/listeners.rs +++ b/packages/yew/src/virtual_dom/listeners.rs @@ -543,10 +543,6 @@ mod tests { } trait Mixin { - fn passive() -> Option { - None - } - fn view(ctx: &Context, state: &State) -> Html where C: Component, @@ -557,8 +553,7 @@ mod tests { } } else { html! { - {state.action} @@ -671,19 +666,6 @@ mod tests { .unwrap(); } - #[test] - async fn passive() { - struct Passive; - - impl Mixin for Passive { - fn passive() -> Option { - Some(true) - } - } - - assert_async::().await; - } - async fn assert_async() { let (link, el) = init::("a"); diff --git a/website/docs/concepts/components/scope.mdx b/website/docs/concepts/components/scope.mdx index 0b070cca9e1..8b60e14cd3a 100644 --- a/website/docs/concepts/components/scope.mdx +++ b/website/docs/concepts/components/scope.mdx @@ -5,7 +5,7 @@ description: "Component's Scope" ## Component's `Scope<_>` API -The component "`Scope`" is the mechanism through which components are able to create callbacks and update themselves +The component "`Scope`" is the mechanism through which components are able to create callbacks and update themselves using messages. We obtain a reference to this by calling `link()` on the context object passed to the component. ### `send_message` @@ -26,8 +26,6 @@ If the given vector is empty, this function doesn't do anything. Create a callback that will send a message to the component when it is executed. Under the hood, it will call `send_message` with the message returned by the provided closure. -There is a different method called `callback_once` which accepts a `FnOnce` instead of a `Fn`. -You should use this with care though, as the resulting callback will panic if executed more than once. ```rust use yew::{html, Component, Context, Html}; @@ -48,16 +46,16 @@ impl Component for Comp { } fn view(&self, ctx: &Context) -> Html { - // Create a callback that accepts some text and sends it + // Create a callback that accepts some text and sends it // to the component as the `Msg::Text` message variant. // highlight-next-line let cb = ctx.link().callback(|text: String| Msg::Text(text)); - + // The previous line is needlessly verbose to make it clearer. // It can be simplified it to this: // highlight-next-line let cb = ctx.link().callback(Msg::Text); - + // Will send `Msg::Text("Hello World!")` to the component. // highlight-next-line cb.emit("Hello World!".to_owned()); @@ -82,6 +80,3 @@ This can be used in cases where, depending on the situation, an update isn't req This is achieved using the `SendAsMessage` trait which is only implemented for these types. You can implement `SendAsMessage` for your own types which allows you to use them in `batch_callback`. - -Like `callback`, this method also has a `FnOnce` counterpart, `batch_callback_once`. -The same restrictions apply as for `callback_once`. diff --git a/website/docs/concepts/router.mdx b/website/docs/concepts/router.mdx index d20f40ebc2a..61a1bab22cc 100644 --- a/website/docs/concepts/router.mdx +++ b/website/docs/concepts/router.mdx @@ -71,7 +71,7 @@ enum Route { fn secure() -> Html { let navigator = use_navigator().unwrap(); - let onclick = Callback::once(move |_| navigator.push(Route::Home)); + let onclick = Callback::from(move |_| navigator.push(Route::Home)); html! {

    { "Secure" }

    @@ -203,7 +203,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::once(move |_| navigator.push(Route::Home)); + let onclick = Callback::from(move |_| navigator.push(Route::Home)); html! { <> @@ -214,7 +214,7 @@ pub fn my_component() -> Html { ``` :::caution -The example here uses `Callback::once`. Use a normal callback if the target route can be the same with the route +The example here uses `Callback::from`. Use a normal callback if the target route can be the same with the route the component is in, or just to play safe. For example, when you have a logo button on every page, 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 the `use_navigator` hook won't trigger a re-render. @@ -236,7 +236,7 @@ pub fn nav_items() -> Html { let go_home_button = { let navigator = navigator.clone(); - let onclick = Callback::once(move |_| navigator.push(Route::Home)); + let onclick = Callback::from(move |_| navigator.push(Route::Home)); html! { } @@ -244,14 +244,14 @@ pub fn nav_items() -> Html { let go_to_first_post_button = { let navigator = navigator.clone(); - let onclick = Callback::once(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! { } }; let go_to_secure_button = { - let onclick = Callback::once(move |_| navigator.push(Route::Secure)); + let onclick = Callback::from(move |_| navigator.push(Route::Secure)); html! { } @@ -275,7 +275,7 @@ identical with the function component case. Here's an example of a view function ```rust ,ignore fn view(&self, ctx: &Context) -> Html { let navigator = ctx.link().navigator().unwrap(); - let onclick = Callback::once(move |_| navigator.push(MainRoute::Home)); + let onclick = Callback::from(move |_| navigator.push(MainRoute::Home)); html!{ }