Support prop extensions for callbacks
Feature Request
It would be nice to be able to create wrapper components that also can pass down callbacks. For example, I'm creating a small select wrapper with some tailwind styling and a label:
#[derive(Props, PartialEq, Clone)]
pub struct SelectProps {
label: String,
variants: Vec<(String, String)>,
// You can extend a specific element or global attributes
#[props(extends = GlobalAttributes, extends = select)]
attributes: Vec<Attribute>,
}
pub fn Select(props: SelectProps) -> Element {
let id_att = props
.attributes
.iter()
.find_map(|att| (att.name == "id").then_some(att.value.clone()));
rsx!(
div {
label { r#for: id_att, class: "text-gray-700 dark:text-gray-200", "{props.label}" }
select {
class: "block w-full px-4 py-2 mt-2 text-gray-700 bg-white border border-gray-200 rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 focus:ring-blue-300 focus:ring-opacity-40 dark:focus:border-blue-300 focus:outline-none focus:ring",
..props.attributes,
for (value , name) in props.variants {
option { value, "{name}" }
}
}
}
)
}
Which I then would like to use like this:
#[component]
fn Example() -> Element {
rsx!(
form {
Select {
label: "Example",
variants: vec![
("Hello".to_string(), "Hello".to_string()),
("Hello".to_string(), "Hello".to_string()),
],
onchange: move |evt| info!("{:?}", evt)
}
}
)
}
However that that gives the following unexpected error:
no method named `onchange` found for struct `SelectPropsBuilder` in the current scope
method not found in `SelectPropsBuilder<((String,), (Vec<(String, String)>,))>
Ideally, this should just work and pass down the callback as expected without requiring anything from the user.
This is a really needed feature. Is it considered as part of the upcoming ver. 0.6, please?
Here is a fixed example from here:
#[derive(Props, PartialEq, Clone)]
struct ButtonProps {
#[props(extends = GlobalAttributes)]
attributes: Vec<Attribute>,
onclick: Option<EventHandler<MouseEvent>>,
}
#[component]
fn ResetButton(props: ButtonProps) -> Element {
rsx! {
button {
class: "reset",
onclick: move |event| if let Some(f) = props.onclick.as_ref() { f(event) }
..props.attributes,
img { src: RESET_ICON }
}
}
}
You would have to do this manually for every callback/"on*" attribute. I doubt this will be included in the 0.6.
@Andrew15-5 Thanks, Andrew! That's great for event handlers.
And maybe I'm missing something, but this looks pretty similar with the example - that FancyButton - from official Event Handlers page, except that in this case an Option of EventHandler<MouseEvent> is used.
And revisiting that page, I realised that there is a simple way to pass a closure that needs to call an async fn.
Here is a recent example:
#[derive(Props, PartialEq, Clone)]
pub struct ConfirmDeleteModalProps {
pub title: String,
pub content: String,
pub action: Signal<Action>,
pub show_delete_confirm: Signal<bool>,
pub delete_handler: EventHandler,
}
#[component]
pub fn ConfirmDeleteModal(props: ConfirmDeleteModalProps) -> Element {
let ConfirmDeleteModalProps {
title,
content,
mut action,
mut show_delete_confirm,
delete_handler,
} = props;
rsx! {
div { class: "fixed inset-0 p-4 flex flex-wrap justify-center items-center w-full h-full z-[1000] before:fixed before:inset-0 before:w-full before:h-full before:bg-[rgba(0,0,0,0.5)] overflow-auto font-[sans-serif]",
div { class: "w-full max-w-lg bg-white shadow-lg rounded-lg p-8 relative",
div {
h4 { class: "text-sm text-gray-800 font-semibold", {title} }
p { class: "text-sm text-gray-600 mt-4", { content } }
}
div { class: "flex justify-between mt-8",
button {
class: "text-red-600 bg-red-50 hover:text-red-800 hover:bg-red-100 drop-shadow-sm px-4 rounded-md",
onclick: move |_| {
show_delete_confirm.set(false);
action.set(Action::Delete);
delete_handler(());
},
"Delete"
}
button {
class: "bg-gray-100 bg-green-100 enabled:hover:bg-green-100 disabled:text-gray-400 hover:disabled:bg-gray-100 drop-shadow-sm px-4 rounded-md",
onclick: move |_| {
show_delete_confirm.set(false);
},
"Cancel"
}
}
}
}
}
}
And that reusable component being used as such:
#[derive(PartialEq, Props, Clone)]
pub struct AttributeDefEditPageProps {
attr_def_id: Id,
}
#[component]
pub fn AttributeDefPage(props: AttributeDefEditPageProps) -> Element {
//
let mut action = use_signal(|| Action::View);
let mut show_delete_confirm = use_signal(|| false);
rsx! {
div { class: "flex flex-col min-h-screen bg-gray-100",
...
if show_delete_confirm() {
ConfirmDeleteModal {
title: "Confirm Delete",
content: "Are you sure you want to delete this attribute definition?",
action,
show_delete_confirm,
delete_handler: move |_| {
spawn(async move {
log::debug!("Calling handle_delete ...");
handle_delete(&id(), action_done, err).await;
});
}
}
}
}
}
}
async fn handle_delete(id: &Id, mut saved: Signal<bool>, mut err: Signal<Option<String>>) {
//
log::debug!(">>> Deleting attribute definition: {:?}", id);
match remove_attr_def(id.clone()).await {
Ok(_) => {
saved.set(true);
err.set(None);
}
Err(e) => {
saved.set(false);
err.set(Some(e.to_string()));
}
}
}
I think this feature could be really helpful for quickly building a component library.
It might also be nice if using #[props(extends = button)] would derive all it's attributes, including global, callbacks etc.