Skip to content

Commit

Permalink
docs: added Callback to documentation and examples. (#1926)
Browse files Browse the repository at this point in the history
* added Callback to documentation and examples.
Also reduced code duplication in Callback implementation.

* added back the closure event callback example
  • Loading branch information
maccesch authored Oct 24, 2023
1 parent 05b4f8e commit e2f6780
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 84 deletions.
44 changes: 41 additions & 3 deletions docs/book/src/view/08_parent_child.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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 you’re
> 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
Expand Down
3 changes: 3 additions & 0 deletions examples/counters/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

[toolchain]
channel = "nightly"
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion examples/login_with_token_csr_only/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub fn App() -> impl IntoView {

// -- callbacks -- //

let on_logout = move || {
let on_logout = move |_| {
logout.dispatch(());
};

Expand Down
11 changes: 5 additions & 6 deletions examples/login_with_token_csr_only/client/src/pages/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -21,15 +21,14 @@ 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;
set_wait_for_response.update(|w| *w = false);
match result {
Ok(res) => {
set_login_error.update(|e| *e = None);
on_success(res);
on_success.call(res);
}
Err(err) => {
let msg = match err {
Expand Down
3 changes: 3 additions & 0 deletions examples/parent_child/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

[toolchain]
channel = "nightly"
22 changes: 4 additions & 18 deletions examples/parent_child/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions leptos_reactive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
82 changes: 34 additions & 48 deletions leptos_reactive/src/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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> {
Expand Down

0 comments on commit e2f6780

Please sign in to comment.