Mutating a dioxus UseState struct that doesn't implement clone

422 views Asked by At

I'm using Dioxus to build a UI. This UI relies heavily on a struct from a third-party library. This struct doesn't implement clone. The struct needs to be modified on button press. Consider this simplified example:

use dioxus::prelude::*;

struct ThirdPartyStruct(i64);

impl ThirdPartyStruct {
    fn add_one(&mut self) {
        self.0 += 1;
    }
}

fn app(cx: Scope) -> Element {
    let third_party_struct = use_state(cx, || ThirdPartyStruct(0));

    cx.render(rsx! {
        button {
            onclick: move |_| third_party_struct.make_mut().add_one(),
            "Click me!"
        }
        p {
            "{third_party_struct.get().0}"
        }
    })
}

This does not compile, since make_mut requires clone to be implemented for ThirdPartyStruct. The real ThirdPartyStruct isn't nearly as simple and the real equivalent of add_one has to be used (so solutions like "use + 1 instead" are invalid). Implementing clone by hand would also be infeasible.

I have tried many things, including most of the methods for the UseState struct meant for modifying the contained struct. All of them either require the struct to be modified by value (like calling + 1 instead of += 1) or need to have clone implemented.

2

There are 2 answers

0
Bea Boxler On BEST ANSWER

The only way I can think of resolving this is wrapping the struct in a RefCell. It might not be the most memory safe way, but it works.

The side effect of this is the state does not know it has changed, so you will need to manually mark the component as dirty each time you call a mutating function with cx.needs_update().

fn app(cx: Scope) -> Element {
    let third_party_struct = use_state(cx, || RefCell::new(ThirdPartyStruct(0)));

    cx.render(rsx! {
        button {
            onclick: move |_| {
                third_party_struct.borrow_mut().add_one();
                cx.needs_update();
            },
            "Click me!"
        }
        p {
            "{third_party_struct.get().borrow().0}"
        }
    })
}

It's worth noting I have not tried this with the sysinfo crate, but it works with your MRE.

0
Evan Almloff On

Dioxus has a separate hook called use_ref for values that are not clone. It is possible to use interior mutability like RefCell inside of a use_ref hook, but it is not recommended because it can easily break updates

use dioxus::prelude::*;

struct ThirdPartyStruct(i64);

impl ThirdPartyStruct {
    fn add_one(&mut self) {
        self.0 += 1;
    }
}

fn app(cx: Scope) -> Element {
    let third_party_struct = use_ref(cx, || ThirdPartyStruct(0));

    cx.render(rsx! {
        button {
            onclick: move |_| third_party_struct.write().add_one(),
            "Click me!"
        }
        p {
            "{third_party_struct.read().0}"
        }
    })
}