Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to convert closure into Callback? #559

Closed
BafDyce opened this issue Aug 4, 2019 · 7 comments
Closed

How to convert closure into Callback? #559

BafDyce opened this issue Aug 4, 2019 · 7 comments

Comments

@BafDyce
Copy link
Contributor

BafDyce commented Aug 4, 2019

Description

I'm submitting a question (check one with "x")

I need to send messages between my components.
In #270 @hgzimmerman explained that the easiest way for this is to do it with callbacks like in the custom_components example. However, instead of sending nothing (()), I want to send back specific messages.

Expected Results

Here are the relevant snippets, the full code can be found here. The problem is with the Callback::from() call.

// map.rs
#[derive(Clone, Default, PartialEq)]
pub struct MapProperties {
    pub map: Map,
    pub onsignal: Option<Callback<MapMsg>>,
}

// app.rs
html! {
    <Map
        map = &self.map
        onsignal = Some(Callback::from(|msg: MapMsg| -> AppMsg {AppMsg::Nop}))
        //onsignal = None
    />
}

I also tried the following which also did not work:

onsignal = Some(|msg: MapMsg| -> AppMsg {AppMsg::Nop})

Actual Results

I get the following error, even though I explicitely specify the return value of the closure.

error[E0271]: type mismatch resolving `<[closure@src/app.rs:52:52: 52:89] as std::ops::FnOnce<(map::MapMsg,)>>::Output == ()`
  --> src/app.rs:52:37
   |
52 |                     onsignal = Some(Callback::from(|msg: MapMsg| -> AppMsg {AppMsg::Nop}))
   |                                     ^^^^^^^^^^^^^^ expected enum `app::AppMsg`, found ()
   |
   = note: expected type `app::AppMsg`
              found type `()`
   = note: required because of the requirements on the impl of `std::convert::From<[closure@src/app.rs:52:52: 52:89]>` for `yew::Callback<map::MapMsg>`
   = note: required by `std::convert::From::from`

error: aborting due to previous error
For more information about this error, try `rustc --explain E0271`.
error: Could not compile `yew-callback-confusion`.

Context (Environment)

  • Rust: rustc 1.37.0-nightly (03ee55bb1 2019-06-01)
  • yew: 0.7.0
  • target: wasm32-unknown-unknown
  • cargo-web: cargo-web 0.6.26
@jstarry
Copy link
Member

jstarry commented Aug 4, 2019

You're pretty close, I think you want to do this:

onsignal = |msg: MapMsg| AppMsg::HandleMapMsg(msg)

Or more simply:

onsignal = AppMsg::HandleMapMsg

@BafDyce
Copy link
Contributor Author

BafDyce commented Aug 4, 2019

Thanks @jstarry

It works but I have two questions:

  1. Why do I not need a Some()? I'm passing an Option so in my understanding I would need an option (but apparently it is wrong in this context?)

  2. Why doesnt the closure work when I explicitely state the return type? In my understanding the following two declarations are exactly the same (as in, they both result in a closure taking an i32 and returning an i32)

|number: i32| number + 1

|number: i32| -> i32 { number + 1 }

So why does one work in that context but not the other?

@jstarry
Copy link
Member

jstarry commented Aug 4, 2019

No problem, thanks for your questions:

  1. Yew is transforming callbacks by wrapping them in an Option behind the scenes. This was confusing behaviour so this will be removed in version 0.8 (Callbacks no longer transform into Option types)

  2. This is a bug in the parser, I just filed a new issue: Callback properties with return types are not parsed correctly #560

@jstarry jstarry closed this as completed Aug 4, 2019
@Wodann
Copy link
Contributor

Wodann commented Aug 21, 2019

@jstarry I have a use case where the callback is actually optional. How would I go about this in version 0.8?

@jstarry
Copy link
Member

jstarry commented Aug 21, 2019

@Wodann you will probably want something like this (untested)

#[derive(Properties)]
pub struct Props {
  optional_cb: Option<Callback<Msg>>
}

...

html! {
  <Component optional_cb=Some(|_| Msg::Callback) />
}

@Wodann
Copy link
Contributor

Wodann commented Aug 21, 2019

That's what I expected too, but if I do it according to that format:

#[derive(Properties)]
pub struct Props {
  pub onup: Option<Callback<()>>,
}
<Component ondown=Some(|_| Msg::Down) />

I get the following compile error:

ondown=Some(|_| Msg::Down) />
    |                        ^^^^ expected struct `yew::Callback`, found closure
    |
    = note: expected type `std::option::Option<yew::Callback<()>>`
               found type `std::option::Option<[closure@src\lib.rs:145:29: 145:42]>```

@jstarry
Copy link
Member

jstarry commented Aug 22, 2019

@Wodann ah it's because we have special handling for transforming closures into callbacks: https://github.com/yewstack/yew/blob/master/src/virtual_dom/vcomp.rs#L143 sorry for misleading you!

Seems like we could add a transformer for optional callbacks too! Feel free to add if you have time: #609

For now, you can use this workaround:

pub struct Callbacks {
    on_click: Callback<(u32, u32)>,
}

pub struct Model {
    props: Props,
    callbacks: Callbacks,
}

// ...

fn create(props: Self::Properties, mut link: ComponentLink<Self>) -> Self {
    let callbacks = Callbacks {
        on_click: link.send_back(|cell| Msg::ClickCell(cell)),
    };

    Model {
        props,
        callbacks,
    }
}

// ...

fn render(&self) -> Html<Self> {
    html! {
        <Child on_click=Some(self.callbacks.on_click.clone()) />
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants