DioxusLabs / dioxus

Fullstack app framework for web, desktop, mobile, and more.
https://dioxuslabs.com
Apache License 2.0
20.34k stars 780 forks source link

Implement DerefMut trait for EventData #1713

Closed Tartopoms closed 9 months ago

Tartopoms commented 9 months ago

Versions:

dioxus = { version = "0.4.0", registry = "custom" }
dioxus-router = { version = "0.4.1", registry = "custom" }
dioxus-web = { version = "0.4.0", registry = "custom" }

Specific Demand

I woulld like to edit Event<FormData> value attribute in order to apply live verification on input change.

rsx!(
    input {
        r#type="number",
        onchange: move |evt| {
            // get a DerefMut with "&mut evt"
            // or directly edit the value if DerefMut trait is implemented
            let len = evt.value.len();
            if len > 3 {
                evt.value = evt.value[len - 3..].to_string()
            }
        }
    }
)

Raised error:

cannot assign to data in an Rc. trait DerefMut is required to modify through a dereference, but it is not implemented for std::rc::Rc<dioxus::events::FormData>

error[E0594]: cannot assign to data in an `Rc`
--> client/src/pages/operations/encrypt.rs:139:49
|
9 | ...                   evt.value = evt.value[len - 3..].to_string();
|                       ^^^^^^^^^ cannot assign
|
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `std::rc::Rc<dioxus::events::FormData>`

Implement Suggestion

I dont know if dyn FileEngine could cause an issue, not how it's supposed to be done, but I guess it would be done thisway:

/// NEW
impl DerefMut for FormData {
    fn deref_mut(&mut self) -> &mut Self {
        self
    }
}

/// ALREADY EXISTS
/* DOMEvent:  Send + SyncTarget relatedTarget */
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone)]
pub struct FormData {
    pub value: String,

    pub values: HashMap<String, Vec<String>>,

    #[cfg_attr(
        feature = "serialize",
        serde(
            default,
            skip_serializing,
            deserialize_with = "deserialize_file_engine"
        )
    )]
    pub files: Option<std::sync::Arc<dyn FileEngine>>,
}
ealmloff commented 9 months ago

If you want to implement a controlled input, there is some documentation about that here. In Dioxus, events are intentionally immutable. They are just data about what the user did, they don't have a two-way binding with the UI. Instead for controlled inputs, you can take that event, derive a new state, and set a state that controls the value of the input to the derived state.

let state = use_state(cx, String::new);
rsx!(
    input {
        r#type="number",
        // Set the input to display the current value of `state`
        value: "{state}",
        onchange: move |evt| {
            // Derive a state from the input
            let derived_state = evt.value[..3].to_string();
            // Set the new state to that derived state
            state.set(derived_state);
        }

    }
)

The type of evt in your original code snippet is Rc<Event<FormData>>. Rc is a reference counted pointer that allows you to hold multiple owning references to a struct (Event<FormData>). Rc does not allow you to get the inner value mutably because that would allow you to hold multiple mutable references to the inner value. You can read more about Rc and it's limitations here.

Tartopoms commented 9 months ago

@ealmloff, indeed it would need to be Arc<Rc<_>>. Thank you for the explenation and documentation reference :)

It also works perfectly fine with a use_ref()

let mech_value = use_ref(cx, Vec::<u8>::new); // size later initialized in an input handler

div {
    class: "bytes-input",
    for i in 0..len {
        input {
            r#type: "number",
            min: "0",
            max: "255",
            value: "{mech_value.with(|v| v[i])}",
            oninput: move |evt| {
                if let Ok(value) = evt.value.parse::<i32>() {
                    mech_value.with_mut(|v| v[i] = value.min(255).max(0) as u8);
                }
            },
        }
    }
}