Spreading doesn't work with events
Feature Request
This is a spin-off from #4011 ("Spread doesn't work with events").
When trying to create reusable components, it's quite jarring that event handlers are somehow not considered attributes.
I realize there's probably some Arcane Arts going on behind the scenes to make event handlers work, but it still leaves library authors in a complicated position.
Here's a minimal example:
use dioxus::prelude::*;
const FAVICON: Asset = asset!("/assets/favicon.ico");
const MAIN_CSS: Asset = asset!("/assets/main.css");
fn main() {
dioxus::launch(App);
}
#[component]
fn App() -> Element {
rsx! {
document::Link { rel: "icon", href: FAVICON }
document::Link { rel: "stylesheet", href: MAIN_CSS }
Hero {}
}
}
#[derive(Clone, Props, PartialEq)]
struct CustomButtonProps {
#[props(extends = button, extends = GlobalAttributes)]
pub attributes: Vec<Attribute>,
}
#[component]
pub fn CustomButton(props: CustomButtonProps) -> Element {
rsx! {
button {
..props.attributes
}
}
}
#[component]
pub fn Hero() -> Element {
rsx! {
CustomButton {
/* vvvv -- compile fails here -- vvvv */
onclick: move |_| { /* useful work.. */ },
}
}
}
We get this error message:
16:15:59 [cargo] error[E0599]: no method named `onclick` found for struct `CustomButtonPropsBuilder` in the current scope
--> bug/src/main.rs:39:13
|
19 | #[derive(Clone, Props, PartialEq)]
| ----- method `onclick` not found for this struct
...
37 | / rsx! {
38 | | CustomButton {
39 | | onclick: move |_| { /* yep */ },
| | -^^^^^^^ method not found in `CustomButtonPropsBuilder<((),)>`
| |____________|
|
From what I can tell about the internals (I'm still trying to understand how it all works), macros are used to derive GlobalAttributes (actually, GlobalAttributesExtension, if I'm not mistaken?), and the same for button-specific attributes).
And events are not covered by this mechanism. Is that mental model accurate?
In any case, it's a big blocker for building any kind of reusable higher-level components. Currently, each and every single potentially-useful event will have to be manually implemented as an Option<EventHandler<T>> (with a concrete T type), and then if-let-Some-style handled in the body.
Needless to say, this doesn't really scale 😄
@ealmloff You've done excellent work in improving prop spreading.
Do you happen to know what the challenge is here? You mentioned in #4011 that this might not be too hard to fix. Or was that referring to something else?
Are events handled very differently from other attributes?
Are events handled very differently from other attributes?
The type inferences on event handlers is slightly worse for components vs elements:
use dioxus::prelude::*;
fn main() {
dioxus::launch(|| {
rsx! {
App {
// This does not compile
onclick: |evt| println!("{:?}", evt.data()),
}
button {
// This does compile
onclick: |evt| println!("{:?}", evt.data()),
}
}
});
}
#[component]
fn App(onclick: EventHandler<MouseEvent>) -> Element {
rsx! {
button {
onclick
}
}
}
In terms of spreading, they should be implemented in a very similar way. We just need to centralize the list of events and add them to the GlobalAttributes trait. Since EventHandler is copy and owned by the props themselves, we will need to generate an owner in the props that derives spread to hold the event handler and handle memorization in place like we do for manual event handlers in components:
impl dioxus_core::prelude::Properties for AppProps
where
Self: Clone,
{
type Builder = AppPropsBuilder<((),)>;
fn builder() -> Self::Builder {
AppProps::builder()
}
fn memoize(&mut self, new: &Self) -> bool {
let equal = self == new;
self.onclick.__point_to(&new.onclick);
if !equal {
let new_clone = new.clone();
}
equal
}
}