Files
dioxus/docs/guide/src/interactivity/useref.md
T
2022-03-28 13:11:33 +08:00

3.7 KiB

UseRef

You might've noticed a fundamental limitation to use_state: to modify the value in-place, it must be cheaply cloneable. But what if your type is not cheap to clone?

In these cases, you should reach for use_ref which is essentially just a glorified Rc<RefCell<T>> (Rust smart pointers).

This provides us some runtime locks around our data, trading reliability for performance. For most cases though, you will find it hard to make use_ref crash.

Note: this is not exactly like its React counterpart since calling write will cause a re-render. For more React parity, use the write_silent method.

To use the hook:

let names = use_ref(&cx, || vec!["susie", "calvin"]);

To read the hook values, use the read() method:

cx.render(rsx!{
    ul {
        names.read().iter().map(|name| {
            rsx!{ "{name}" }
        })
    }
})

And then to write into the hook value, use the write method:

names.write().push("Tiger");

If you don't want to re-render the component when names is updated, then we can use the write_silent method:

names.write_silent().push("Transmogrifier");

Again, like UseState, the UseRef handle is clonable into async contexts:

// infinitely push calvin into the list
cx.spawn({
    to_owned![names];
    async move {
        loop {
            wait_ms(100).await;
            names.write().push("Calvin");
        }
    }
})

Using UseRef for complex state

The best use case of use_ref is to manage "complex" data local to your component. This should be things like large structs, collections, queues, state machines, and other data where cloning would be expensive.

let val = use_ref(&cx, || vec![1, 3, 3, 7]);
let val = use_ref(&cx, || (0..10000).collect::<Vec<_>x>());
let val = use_ref(&cx, || Configuration {
    a: "asdasd",
    // .. more complex fields
});

UseRef can be sent down into child components too.

UseRef memoizes with itself, performing a cheap pointer comparison. If the UseRef handle is the same, then the component can be memoized.


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

    cx.render(rsx!{
        Child { val: val.clone() }
    })
}

#[inline_props]
fn Child(cx: Scope, val: UseRef<i32>) -> Element {
    // ...
}

Using UseRef with "models"

One option for state management that UseRef enables is the development of a "model" for your components. This particular pattern enables you to model your state with regular structs.

For instance, our calculator example uses a struct to model the state.


struct Calculator {
    display_value: String,
    operator: Option<Operator>,
    waiting_for_operand: bool,
    cur_val: f64,
}

Our component is really simple - we just call use_ref to get an initial calculator state.

fn app(cx: Scope) -> Element {
    let state = use_ref(&cx, Calculator::new);

    cx.render(rsx!{
        // the rendering code
    })
}

In our UI, we can then use read and a helper method to get data out of the model.

// Our accessor method
impl Calculator {
    fn formatted_display(&self) -> String {
        self.display_value
            .parse::<f64>()
            .unwrap()
            .separated_string()
    }
}

// And then in the UI
cx.render(rsx!{
    div { [state.read().formatted_display()] }
})

To modify the state, we can setup a helper method and then attach it to a callback.

// our helper
impl Calculator {
    fn clear_display(&mut self) {
        self.display_value = "0".to_string()
    }
}

// our callback
cx.render(rsx!{
    button {
        onclick: move |_| state.write().clear_display(),
    }
})