rustyew

How to get a state that was moved to a callback in Yew.rs?


I am trying to get the value of title_state, which is located inside a callback of the main app component.

#[function_component(App)]
fn app() -> Html {
    let title_state = use_state(|| String::default());

    let title_change_cb = Callback::from(move |v:String| {
        let title_state = title_state.clone();
        title_state.set(v);
    });


    html! {
        <>
            <TitleController callback={title_change_cb}/>
            <p>{*title_state}</p>
        </>
    }
} 

The same callback is passed to TitleController.

#[derive(Properties, PartialEq)]
pub struct TitleControllerProps {
    pub callback: Callback<String>
}

#[function_component(TitleController)]
pub fn title_controller(props: &TitleControllerProps) -> Html {
    let callback = Callback::clone(&props.callback);
    let input_nr = use_node_ref();

    let onchange = {
        let input_nr = input_nr.clone();
        Callback::from(move |_:Event| {
            let input = input_nr.cast::<HtmlInputElement>();
            if let Some(input) = input {
                callback.emit(input.value());
            }
        })
    };
    html! {
        <>
            <h1>{"Title Controller"}</h1>
            <input ref={input_nr}
            {onchange}
            id="my-input"
            type="text"
            />
        </>
    }
}

So far i have only tried common solutions like clone(), using immutable references but nothing works.

Errors i get:

With dereference operator only:

error[E0507]: cannot move out of dereference of `UseStateHandle<String>`
  --> src/main.rs:18:17
   |
15 | /     html! {
16 | |         <>
17 | |             <TitleController callback={title_change_cb}/>
18 | |             <p>{*title_state}</p>
   | |                 ^^^^^^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait
19 | |         </>
20 | |     }
   | |_____- value moved due to this method call
   |

When cloning dereferenced state:

error[E0382]: borrow of moved value: `title_state`
   --> src/main.rs:18:17
    |
7   |     let title_state = use_state(|| String::default());
    |         ----------- move occurs because `title_state` has type `UseStateHandle<String>`, which does not implement the `Copy` trait
8   |
9   |     let title_change_cb = Callback::from(move |v:String| {
    |                                          --------------- value moved into closure here
10  |         let title_state = title_state.clone();
    |                           ----------- variable moved due to use in closure
...
18  |             <p>{(*title_state).clone()}</p>
    |                 ^^^^^^^^^^^^^^ value borrowed here after move
    |
    = note: borrow occurs due to deref coercion to `String`

Solution

  • Your second version is almost correct, you just have to clone the title_state before you pass it to the closure so you only move the copy into the closure and still have a handle to the state afterwards:

        let title_change_cb = Callback::from({
            let title_state = title_state.clone(); // <-- clone here
            move |v: String| { // <-- don't clone inside of this
                title_state.set(v);
            }
        });