leptos/
callback.rs

1//! Callbacks define a standard way to store functions and closures. They are useful
2//! for component properties, because they can be used to define optional callback functions,
3//! which generic props don’t support.
4//!
5//! # Usage
6//! Callbacks can be created manually from any function or closure, but the easiest way
7//! to create them is to use `#[prop(into)]]` when defining a component.
8//! ```
9//! use leptos::prelude::*;
10//!
11//! #[component]
12//! fn MyComponent(
13//!     #[prop(into)] render_number: Callback<(i32,), String>,
14//! ) -> impl IntoView {
15//!     view! {
16//!         <div>
17//!             {render_number.run((1,))}
18//!             // callbacks can be called multiple times
19//!             {render_number.run((42,))}
20//!         </div>
21//!     }
22//! }
23//! // you can pass a closure directly as `render_number`
24//! fn test() -> impl IntoView {
25//!     view! {
26//!         <MyComponent render_number=|x: i32| x.to_string()/>
27//!     }
28//! }
29//! ```
30//!
31//! *Notes*:
32//! - The `render_number` prop can receive any type that implements `Fn(i32) -> String`.
33//! - Callbacks are most useful when you want optional generic props.
34//! - All callbacks implement the [`Callable`](leptos::callback::Callable) trait, and can be invoked with `my_callback.run(input)`.
35//! - The callback types implement [`Copy`], so they can easily be moved into and out of other closures, just like signals.
36//!
37//! # Types
38//! This modules implements 2 callback types:
39//! - [`Callback`](leptos::callback::Callback)
40//! - [`UnsyncCallback`](leptos::callback::UnsyncCallback)
41//!
42//! Use `SyncCallback` if the function is not `Sync` and `Send`.
43
44use reactive_graph::{
45    owner::{LocalStorage, StoredValue},
46    traits::{Dispose, WithValue},
47};
48use std::{fmt, rc::Rc, sync::Arc};
49
50/// A wrapper trait for calling callbacks.
51pub trait Callable<In: 'static, Out: 'static = ()> {
52    /// calls the callback with the specified argument.
53    ///
54    /// Returns None if the callback has been disposed
55    fn try_run(&self, input: In) -> Option<Out>;
56    /// calls the callback with the specified argument.
57    ///
58    /// # Panics
59    /// Panics if you try to run a callback that has been disposed
60    fn run(&self, input: In) -> Out;
61}
62
63/// A callback type that is not required to be `Send + Sync`.
64pub struct UnsyncCallback<In: 'static, Out: 'static = ()>(
65    StoredValue<Rc<dyn Fn(In) -> Out>, LocalStorage>,
66);
67
68impl<In> fmt::Debug for UnsyncCallback<In> {
69    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
70        fmt.write_str("Callback")
71    }
72}
73
74impl<In, Out> Copy for UnsyncCallback<In, Out> {}
75
76impl<In, Out> Clone for UnsyncCallback<In, Out> {
77    fn clone(&self) -> Self {
78        *self
79    }
80}
81
82impl<In, Out> Dispose for UnsyncCallback<In, Out> {
83    fn dispose(self) {
84        self.0.dispose();
85    }
86}
87
88impl<In, Out> UnsyncCallback<In, Out> {
89    /// Creates a new callback from the given function.
90    pub fn new<F>(f: F) -> UnsyncCallback<In, Out>
91    where
92        F: Fn(In) -> Out + 'static,
93    {
94        Self(StoredValue::new_local(Rc::new(f)))
95    }
96
97    /// Returns `true` if both callbacks wrap the same underlying function pointer.
98    #[inline]
99    pub fn matches(&self, other: &Self) -> bool {
100        self.0.with_value(|self_value| {
101            other
102                .0
103                .with_value(|other_value| Rc::ptr_eq(self_value, other_value))
104        })
105    }
106}
107
108impl<In: 'static, Out: 'static> Callable<In, Out> for UnsyncCallback<In, Out> {
109    fn try_run(&self, input: In) -> Option<Out> {
110        self.0.try_with_value(|fun| fun(input))
111    }
112
113    fn run(&self, input: In) -> Out {
114        self.0.with_value(|fun| fun(input))
115    }
116}
117
118macro_rules! impl_unsync_callable_from_fn {
119    ($($arg:ident),*) => {
120        impl<F, $($arg,)* T, Out> From<F> for UnsyncCallback<($($arg,)*), Out>
121        where
122            F: Fn($($arg),*) -> T + 'static,
123            T: Into<Out> + 'static,
124            $($arg: 'static,)*
125        {
126            fn from(f: F) -> Self {
127                paste::paste!(
128                    Self::new(move |($([<$arg:lower>],)*)| f($([<$arg:lower>]),*).into())
129                )
130            }
131        }
132    };
133}
134
135impl_unsync_callable_from_fn!();
136impl_unsync_callable_from_fn!(P1);
137impl_unsync_callable_from_fn!(P1, P2);
138impl_unsync_callable_from_fn!(P1, P2, P3);
139impl_unsync_callable_from_fn!(P1, P2, P3, P4);
140impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5);
141impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6);
142impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7);
143impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8);
144impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9);
145impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10);
146impl_unsync_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11);
147impl_unsync_callable_from_fn!(
148    P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12
149);
150
151/// Callbacks define a standard way to store functions and closures.
152///
153/// # Example
154/// ```
155/// # use leptos::prelude::*;
156/// # use leptos::callback::{Callable, Callback};
157/// #[component]
158/// fn MyComponent(
159///     #[prop(into)] render_number: Callback<(i32,), String>,
160/// ) -> impl IntoView {
161///     view! {
162///         <div>
163///             {render_number.run((42,))}
164///         </div>
165///     }
166/// }
167///
168/// fn test() -> impl IntoView {
169///     view! {
170///         <MyComponent render_number=move |x: i32| x.to_string()/>
171///     }
172/// }
173/// ```
174pub struct Callback<In, Out = ()>(
175    StoredValue<Arc<dyn Fn(In) -> Out + Send + Sync>>,
176)
177where
178    In: 'static,
179    Out: 'static;
180
181impl<In, Out> fmt::Debug for Callback<In, Out> {
182    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
183        fmt.write_str("SyncCallback")
184    }
185}
186
187impl<In, Out> Callable<In, Out> for Callback<In, Out> {
188    fn try_run(&self, input: In) -> Option<Out> {
189        self.0.try_with_value(|fun| fun(input))
190    }
191
192    fn run(&self, input: In) -> Out {
193        self.0.with_value(|f| f(input))
194    }
195}
196
197impl<In, Out> Clone for Callback<In, Out> {
198    fn clone(&self) -> Self {
199        *self
200    }
201}
202
203impl<In, Out> Dispose for Callback<In, Out> {
204    fn dispose(self) {
205        self.0.dispose();
206    }
207}
208
209impl<In, Out> Copy for Callback<In, Out> {}
210
211macro_rules! impl_callable_from_fn {
212    ($($arg:ident),*) => {
213        impl<F, $($arg,)* T, Out> From<F> for Callback<($($arg,)*), Out>
214        where
215            F: Fn($($arg),*) -> T + Send + Sync + 'static,
216            T: Into<Out> + 'static,
217            $($arg: Send + Sync + 'static,)*
218        {
219            fn from(f: F) -> Self {
220                paste::paste!(
221                    Self::new(move |($([<$arg:lower>],)*)| f($([<$arg:lower>]),*).into())
222                )
223            }
224        }
225    };
226}
227
228impl_callable_from_fn!();
229impl_callable_from_fn!(P1);
230impl_callable_from_fn!(P1, P2);
231impl_callable_from_fn!(P1, P2, P3);
232impl_callable_from_fn!(P1, P2, P3, P4);
233impl_callable_from_fn!(P1, P2, P3, P4, P5);
234impl_callable_from_fn!(P1, P2, P3, P4, P5, P6);
235impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7);
236impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8);
237impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9);
238impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10);
239impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11);
240impl_callable_from_fn!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12);
241
242impl<In: 'static, Out: 'static> Callback<In, Out> {
243    /// Creates a new callback from the given function.
244    pub fn new<F>(fun: F) -> Self
245    where
246        F: Fn(In) -> Out + Send + Sync + 'static,
247    {
248        Self(StoredValue::new(Arc::new(fun)))
249    }
250
251    /// Returns `true` if both callbacks wrap the same underlying function pointer.
252    #[inline]
253    pub fn matches(&self, other: &Self) -> bool {
254        self.0
255            .try_with_value(|self_value| {
256                other.0.try_with_value(|other_value| {
257                    Arc::ptr_eq(self_value, other_value)
258                })
259            })
260            .flatten()
261            .unwrap_or(false)
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::Callable;
268    use crate::callback::{Callback, UnsyncCallback};
269    use reactive_graph::traits::Dispose;
270
271    struct NoClone {}
272
273    #[test]
274    fn clone_callback() {
275        let callback = Callback::new(move |_no_clone: NoClone| NoClone {});
276        let _cloned = callback;
277    }
278
279    #[test]
280    fn clone_unsync_callback() {
281        let callback =
282            UnsyncCallback::new(move |_no_clone: NoClone| NoClone {});
283        let _cloned = callback;
284    }
285
286    #[test]
287    fn runback_from() {
288        let _callback: Callback<(), String> = (|| "test").into();
289        let _callback: Callback<(i32, String), String> =
290            (|num, s| format!("{num} {s}")).into();
291    }
292
293    #[test]
294    fn sync_callback_from() {
295        let _callback: UnsyncCallback<(), String> = (|| "test").into();
296        let _callback: UnsyncCallback<(i32, String), String> =
297            (|num, s| format!("{num} {s}")).into();
298    }
299
300    #[test]
301    fn sync_callback_try_run() {
302        let callback = Callback::new(move |arg| arg);
303        assert_eq!(callback.try_run((0,)), Some((0,)));
304        callback.dispose();
305        assert_eq!(callback.try_run((0,)), None);
306    }
307
308    #[test]
309    fn unsync_callback_try_run() {
310        let callback = UnsyncCallback::new(move |arg| arg);
311        assert_eq!(callback.try_run((0,)), Some((0,)));
312        callback.dispose();
313        assert_eq!(callback.try_run((0,)), None);
314    }
315
316    #[test]
317    fn callback_matches_same() {
318        let callback1 = Callback::new(|x: i32| x * 2);
319        let callback2 = callback1;
320        assert!(callback1.matches(&callback2));
321    }
322
323    #[test]
324    fn callback_matches_different() {
325        let callback1 = Callback::new(|x: i32| x * 2);
326        let callback2 = Callback::new(|x: i32| x + 1);
327        assert!(!callback1.matches(&callback2));
328    }
329
330    #[test]
331    fn unsync_callback_matches_same() {
332        let callback1 = UnsyncCallback::new(|x: i32| x * 2);
333        let callback2 = callback1;
334        assert!(callback1.matches(&callback2));
335    }
336
337    #[test]
338    fn unsync_callback_matches_different() {
339        let callback1 = UnsyncCallback::new(|x: i32| x * 2);
340        let callback2 = UnsyncCallback::new(|x: i32| x + 1);
341        assert!(!callback1.matches(&callback2));
342    }
343}