dioxus icon indicating copy to clipboard operation
dioxus copied to clipboard

Executing JS from Dioxus doesn't yield the same result as from browser console.

Open bishoyroufael opened this issue 2 years ago • 8 comments

Problem

Executing java-script through dioxus doesn't produce the correct expected result.

I am trying to do the following:

 // Dioxus component
 // ...
    let oninput = move |e: Event<FormData>| {
        let res = execute_js(&cx, "var ot=window.getSelection().baseNode.parentElement.offsetTop; dioxus.send(ot); console.log(ot);");
        println!("{:?}", res);
        println!("oninput data value: {:?}", e.data.value);
    };

This JS code should return an offset from the top of an element.

Steps To Reproduce

Expected behavior

Both results from dioxus.send(...) and the browser console should match.

Preview

dioxus-bug

Environment:

  • Dioxus version: 0.4.1
  • Rust version: 1.76.0
  • OS info: Window 10
  • App platform: desktop

Questionnaire

  • [x] I'm interested in fixing this myself but don't know where to start
  • [ ] I would like to fix and I have a solution
  • [ ] I don't have time to fix this right now, but maybe later

bishoyroufael avatar Feb 15 '24 14:02 bishoyroufael

Issue was from my side not calling .restart(...) on the use_future hook. However, I still don't understand why the first value is always None on Dioxus side

bishoyroufael avatar Feb 15 '24 14:02 bishoyroufael

What is execute_js?

ealmloff avatar Feb 15 '24 15:02 ealmloff

A small wrapper to run js and retrieve the value

// handy utility method to execute js
pub fn execute_js<'a, T>(cx: &'a Scope<'a, T>, js_code: &str) -> Option<&'a Value> {
    // Use eval returns a function that can spawn eval instances
    let create_eval = use_eval(cx);

    // You can create as many eval instances as you want
    let eval = create_eval(js_code).unwrap();


    let future = use_future(cx, (), |_| {
        to_owned![eval];
        async move {
            // You can receive any message from JavaScript with the recv method
            eval.recv().await.unwrap()
        }
    });

    future.restart();

    future.value()
}

bishoyroufael avatar Feb 15 '24 15:02 bishoyroufael

You can't use hooks inside of event handlers, or any code that runs conditionally. It violates the rules of hooks

ealmloff avatar Feb 15 '24 16:02 ealmloff

What's the recommended way of having an entity which you can throw some js commands to it and get the result?

I tried the following as well:

pub fn TArea(cx: Scope<TAreaProps>) -> Element {
// .. Component
    let create_eval = use_eval(cx);
    let eval = use_state(cx, || create_eval("").unwrap());
    let future = use_future(cx, (), |_| {
        to_owned![eval];
        async move {
            eval.recv().await.unwrap()
        }
    });
    
    let oninput = move |e: Event<FormData>| {
        eval.set(create_eval(GET_INITIAL_EDITABLE_AREA_OFFSET_TOP_LEFT).unwrap());
        future.restart();
        let res = future.value();
        println!("{:?}", res);
    }
    // ... rest

This gives a similar behaviour with the first value being always None on Dioxus side

bishoyroufael avatar Feb 15 '24 16:02 bishoyroufael

calling .value() on a future will get the current value of the future. I think in 0.4.1, futures are not polled until the end of the render, so it will at least resolve to None once.

If you want to get a value immediately without a future, you can manually create a function with js-sys. The eval API in dioxus is cross platform and some platforms require all results to be async

ealmloff avatar Feb 15 '24 18:02 ealmloff

But js-sys isn't going to work with the desktop platforms, right?

bishoyroufael avatar Feb 15 '24 19:02 bishoyroufael

Exactly, if you need to target anything more than web, the API needs to be async which means the use_future will be None before it is resolved

ealmloff avatar Feb 15 '24 20:02 ealmloff