Search code examples
rustyew

Show input value in browser using Yew Rust


I am starting to learn Rust with Yew. I followed the counter example in the documentation and now I am trying to implement an input functionality. I want to show the input value in html. I am able to console.log it but not to render it in the browser.

What am I missing?

This is the code I have so far:

use wasm_bindgen::{JsCast, UnwrapThrowExt};
use web_sys::HtmlInputElement;
use yew::prelude::*;

#[function_component]
fn App() -> Html {
    let name = use_state(|| "");
    let oninput = Callback::from(move |input_event: InputEvent| {
        let name = name.clone();
        let target: HtmlInputElement = input_event
            .target()
            .unwrap_throw()
            .dyn_into()
            .unwrap_throw();
        //web_sys::console::log_1(&target.value().into()); // <- can console the value.
        move |_: HtmlInputElement| name.set(&target.value().as_str());
    });

    html! {
        <div>
            <input {oninput}  />
            <p>{"name: "}<h5>{name}</h5></p> // <-- here is the error
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}

But I get this error:

error[E0277]: `UseStateHandle<&str>` doesn't implement `std::fmt::Display`
  --> src/main.rs:22:29
   |
22 |             <p>{"name"}<h5>{name}</h5></p>
   |                             ^^^^
   |                             |
   |                             `UseStateHandle<&str>` cannot be formatted with the default formatter
   |                             required by a bound introduced by this call
   |
   = help: the trait `std::fmt::Display` is not implemented for `UseStateHandle<&str>`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = note: required for `UseStateHandle<&str>` to implement `ToString`
   = note: required for `VNode` to implement `From<UseStateHandle<&str>>`
   = note: required for `UseStateHandle<&str>` to implement `Into<VNode>`
   = note: 2 redundant requirements hidden
   = note: required for `UseStateHandle<&str>` to implement `Into<NodeSeq<UseStateHandle<&str>, VNode>>`

I tried to use name.get(), &name, name.clone() and combination of those but I always get an error. Could you explain me why I cannot get the value to show in the browser?

I appreciate any help.


Solution

  • To use the value in the state, you need to dereference it:

    <p>{"name: "}<h5>{*name}</h5></p>
    

    However, now you will see another errors:

    error[E0716]: temporary value dropped while borrowed
      --> src/main.rs:23:46
       |
    16 |         let name = name.clone();
       |             ---- lifetime `'1` appears in the type of `name`
    ...
    23 |         move |_: HtmlInputElement| name.set(&target.value().as_str());
       |                                    ----------^^^^^^^^^^^^^^----------
       |                                    |         |                      |
       |                                    |         |                      temporary value is freed at the end of this statement
       |                                    |         creates a temporary value which is freed while still in use
       |                                    argument requires that borrow lasts for `'1`
    
    error[E0382]: borrow of moved value: `name`
       --> src/main.rs:29:31
        |
    14  |     let name = use_state(|| "");
        |         ---- move occurs because `name` has type `UseStateHandle<&str>`, which does not implement the `Copy` trait
    15  |     let oninput = Callback::from(move |input_event: InputEvent| {
        |                                  ------------------------------ value moved into closure here
    16  |         let name = name.clone();
        |                    ---- variable moved due to use in closure
    ...
    29  |             <p>{"name: "}<h5>{*name}</h5></p> // <-- here is the error
        |                               ^^^^^ value borrowed here after move
        |
        = note: borrow occurs due to deref coercion to `&str`
    note: deref defined here
       --> /home/explorer/.cargo/registry/src/github.com-1ecc6299db9ec823/yew-0.20.0/src/functional/hooks/use_state.rs:128:5
        |
    128 |     type Target = T;
        |     ^^^^^^^^^^^
    

    The first error is because you need to store String and not &str. The second is because you need to clone() the state before the closure:

    #[function_component]
    fn App() -> Html {
        let name = use_state(|| String::new());
        let oninput = Callback::from({
            let name = name.clone();
            move |input_event: InputEvent| {
                let target: HtmlInputElement = input_event
                    .target()
                    .unwrap_throw()
                    .dyn_into()
                    .unwrap_throw();
                //web_sys::console::log_1(&target.value().into()); // <- can console the value.
                name.set(target.value());
            }
        });
    
        html! {
            <div>
                <input {oninput}  />
                <p>{"name: "}<h5>{&*name}</h5></p>
            </div>
        }
    }