docs: added Callback to documentation and examples. (#1926)

* added Callback to documentation and examples.
Also reduced code duplication in Callback implementation.

* added back the closure event callback example
This commit is contained in:
Marc-Stefan Cassola 2023-10-24 19:14:51 +01:00 committed by GitHub
parent 05b4f8e617
commit e2f6780de4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 97 additions and 84 deletions

View file

@ -72,9 +72,7 @@ pub fn App() -> impl IntoView {
#[component]
pub fn ButtonB<F>(on_click: F) -> impl IntoView
where
F: Fn(MouseEvent) + 'static,
pub fn ButtonB(#[prop(into)] on_click: Callback<MouseEvent>) -> impl IntoView
{
view! {
<button on:click=on_click>
@ -90,10 +88,50 @@ of keeping local state local, preventing the problem of spaghetti mutation. But
the logic to mutate that signal needs to exist up in `<App/>`, not down in `<ButtonB/>`. These
are real trade-offs, not a simple right-or-wrong choice.
> Note the way we use the `Callback<In, Out>` type. This is basically a
> wrapper around a closure `Fn(In) -> Out` that is also `Copy` and makes it
> easy to pass around.
>
> We also used the `#[prop(into)]` attribute so we can pass a normal closure into
> `on_click`. Please see the [chapter "`into` Props"](./03_components.md#into-props) for more details.
### 2.1 Use Closure instead of `Callback`
You can use a Rust closure `Fn(MouseEvent)` directly instead of `Callback`:
```rust
#[component]
pub fn App() -> impl IntoView {
let (toggled, set_toggled) = create_signal(false);
view! {
<p>"Toggled? " {toggled}</p>
<ButtonB on_click=move |_| set_toggled.update(|value| *value = !*value)/>
}
}
#[component]
pub fn ButtonB<F>(on_click: F) -> impl IntoView
where
F: Fn(MouseEvent) + 'static,
pub fn ButtonB(#[prop(into)] on_click: Callback<MouseEvent>) -> impl IntoView
{
view! {
<button on:click=on_click>
"Toggle"
</button>
}
}
```
The code is very similar in this case. On more advanced use-cases using a
closure might require some cloning compared to using a `Callback`.
> Note the way we declare the generic type `F` here for the callback. If youre
> confused, look back at the [generic props](./03_components.html#generic-props) section
> of the chapter on components.
## 3. Use an Event Listener
You can actually write Option 2 in a slightly different way. If the callback maps directly onto

View file

@ -0,0 +1,3 @@
[toolchain]
channel = "nightly"

View file

@ -3,10 +3,10 @@ use leptos::*;
use leptos_router::*;
#[component]
pub fn NavBar<F>(logged_in: Signal<bool>, on_logout: F) -> impl IntoView
where
F: Fn() + 'static + Clone,
{
pub fn NavBar(
logged_in: Signal<bool>,
#[prop(into)] on_logout: Callback<()>,
) -> impl IntoView {
view! {
<nav>
<Show
@ -21,10 +21,7 @@ where
>
<a
href="#"
on:click={
let on_logout = on_logout.clone();
move |_| on_logout()
}
on:click=move |_| on_logout.call(())
>
"Logout"
</a>

View file

@ -57,7 +57,7 @@ pub fn App() -> impl IntoView {
// -- callbacks -- //
let on_logout = move || {
let on_logout = move |_| {
logout.dispatch(());
};

View file

@ -8,10 +8,10 @@ use leptos::*;
use leptos_router::*;
#[component]
pub fn Login<F>(api: UnauthorizedApi, on_success: F) -> impl IntoView
where
F: Fn(AuthorizedApi) + 'static + Clone,
{
pub fn Login(
api: UnauthorizedApi,
#[prop(into)] on_success: Callback<AuthorizedApi>,
) -> impl IntoView {
let (login_error, set_login_error) = create_signal(None::<String>);
let (wait_for_response, set_wait_for_response) = create_signal(false);
@ -21,7 +21,6 @@ where
let email = email.to_string();
let password = password.to_string();
let credentials = Credentials { email, password };
let on_success = on_success.clone();
async move {
set_wait_for_response.update(|w| *w = true);
let result = api.login(&credentials).await;
@ -29,7 +28,7 @@ where
match result {
Ok(res) => {
set_login_error.update(|e| *e = None);
on_success(res);
on_success.call(res);
}
Err(err) => {
let msg = match err {

View file

@ -0,0 +1,3 @@
[toolchain]
channel = "nightly"

View file

@ -74,13 +74,11 @@ pub fn ButtonA(
/// Button B receives a closure
#[component]
pub fn ButtonB<F>(
pub fn ButtonB(
/// Callback that will be invoked when the button is clicked.
on_click: F,
) -> impl IntoView
where
F: Fn(MouseEvent) + 'static,
{
#[prop(into)]
on_click: Callback<MouseEvent>,
) -> impl IntoView {
view! {
<button
@ -89,18 +87,6 @@ where
"Toggle Right"
</button>
}
// just a note: in an ordinary function ButtonB could take on_click: impl Fn(MouseEvent) + 'static
// and save you from typing out the generic
// the component macro actually expands to define a
//
// struct ButtonBProps<F> where F: Fn(MouseEvent) + 'static {
// on_click: F
// }
//
// this is what allows us to have named props in our component invocation,
// instead of an ordered list of function arguments
// if Rust ever had named function arguments we could drop this requirement
}
/// Button C is a dummy: it renders a button but doesn't handle

View file

@ -43,6 +43,7 @@ cfg-if = "1"
indexmap = "2"
self_cell = "1.0.0"
pin-project = "1"
paste = "1"
[dev-dependencies]
criterion = { version = "0.5.1", features = ["html_reports"] }

View file

@ -107,31 +107,41 @@ impl<In: 'static, Out: 'static> Callable<In, Out> for Callback<In, Out> {
}
}
#[cfg(not(feature = "nightly"))]
impl<F, In, T, Out> From<F> for Callback<In, Out>
where
F: Fn(In) -> T + 'static,
T: Into<Out> + 'static,
{
fn from(f: F) -> Callback<In, Out> {
Callback::new(move |x| f(x).into())
}
macro_rules! impl_from_fn {
($ty:ident) => {
#[cfg(not(feature = "nightly"))]
impl<F, In, T, Out> From<F> for $ty<In, Out>
where
F: Fn(In) -> T + 'static,
T: Into<Out> + 'static,
{
fn from(f: F) -> Self {
Self::new(move |x| f(x).into())
}
}
paste::paste! {
#[cfg(feature = "nightly")]
auto trait [<NotRaw $ty>] {}
#[cfg(feature = "nightly")]
impl<A, B> ![<NotRaw $ty>] for $ty<A, B> {}
#[cfg(feature = "nightly")]
impl<F, In, T, Out> From<F> for $ty<In, Out>
where
F: Fn(In) -> T + [<NotRaw $ty>] + 'static,
T: Into<Out> + 'static,
{
fn from(f: F) -> Self {
Self::new(move |x| f(x).into())
}
}
}
};
}
#[cfg(feature = "nightly")]
auto trait NotRawCallback {}
#[cfg(feature = "nightly")]
impl<A, B> !NotRawCallback for Callback<A, B> {}
#[cfg(feature = "nightly")]
impl<F, In, T, Out> From<F> for Callback<In, Out>
where
F: Fn(In) -> T + NotRawCallback + 'static,
T: Into<Out> + 'static,
{
fn from(f: F) -> Callback<In, Out> {
Callback::new(move |x| f(x).into())
}
}
impl_from_fn!(Callback);
#[cfg(feature = "nightly")]
impl<In, Out> FnOnce<(In,)> for Callback<In, Out> {
@ -190,31 +200,7 @@ impl<In: 'static, Out: 'static> SyncCallback<In, Out> {
}
}
#[cfg(not(feature = "nightly"))]
impl<F, In, T, Out> From<F> for SyncCallback<In, Out>
where
F: Fn(In) -> T + 'static,
T: Into<Out> + 'static,
{
fn from(f: F) -> SyncCallback<In, Out> {
SyncCallback::new(move |x| f(x).into())
}
}
#[cfg(feature = "nightly")]
auto trait NotRawSyncCallback {}
#[cfg(feature = "nightly")]
impl<A, B> !NotRawSyncCallback for SyncCallback<A, B> {}
#[cfg(feature = "nightly")]
impl<F, In, T, Out> From<F> for SyncCallback<In, Out>
where
F: Fn(In) -> T + NotRawSyncCallback + 'static,
T: Into<Out> + 'static,
{
fn from(f: F) -> SyncCallback<In, Out> {
SyncCallback::new(move |x| f(x).into())
}
}
impl_from_fn!(SyncCallback);
#[cfg(feature = "nightly")]
impl<In, Out> FnOnce<(In,)> for SyncCallback<In, Out> {