yew icon indicating copy to clipboard operation
yew copied to clipboard

Calling `suspense::use_future` makes children `use_effect` hooks execution inconsistent.

Open lowlevl opened this issue 1 year ago • 2 comments

Problem EDIT: This initially mentioned the leaflet library, but upon testing more, I ended up minimizing the example even further and getting rid of leaflet while still getting inconsistencies with suspense::use_future.

The use of suspense::use_future makes subsequent uses of use_effect execute before the DOM is updated, which is unexpected and inconsistent with the expected behavior.

Steps To Reproduce

  1. Use yew = { version = "0.21.0", features = ["csr"] } or yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] } and web-sys = { version = "0.3.76", features = ["HtmlElement"] }.
  2. Use this minimal example:
use yew::prelude::*;

fn main() {
    #[function_component]
    fn View() -> HtmlResult {
        // Un-commenting the following line makes the application panic on the `expect`.
        // yew::suspense::use_future(|| async {})?;

        use_effect(|| {
            web_sys::window()
                .unwrap()
                .document()
                .unwrap()
                .get_element_by_id("foo")
                .expect("Element `#foo` should be rendered when the `use_effect` hook is called.");
        });

        Ok(html! {
            <div id="foo"></div>
        })
    }

    #[function_component]
    fn App() -> Html {
        html! {<Suspense><View /></Suspense>}
    }

    yew::Renderer::<App>::new().render();
}
  1. Adding back the commented use_future call breaks the get_element_by_id call and makes the application panic.

Expected behavior I may misunderstand the usage of use_effect, but from what I understand, it is analogous to the Component::rendered method, and I would expect the use_effect (or Component::rendered) hook to be executed after the DOM has been updated in all cases. However when a suspension arises, the use_effect (or Component::rendered) hook seems to be executed before the updated DOM becomes available to the JS (web-sys in this case) context.

Debug tools logs

  • With the call to use_future commented: nothing (expected).
  • With the call to use_future un-commented: panics with,
panicked at src/main.rs:91:18:
Element `#foo` should be rendered when the `use_effect` hook is called.

Stack:

__wbg_get_imports/imports.wbg.__wbg_new_8a6f238a6ece86ea@http://0.0.0.0:8080/maap-66a67bc41865f4a6.js:420:21
maap-9136cacef662140e.wasm.__wbg_new_8a6f238a6ece86ea externref shim@http://0.0.0.0:8080/maap-66a67bc41865f4a6_bg.wasm:wasm-function[4516]:0x10e274
maap-9136cacef662140e.wasm.console_error_panic_hook::Error::new::hc724c5e5fa09d029@http://0.0.0.0:8080/maap-66a67bc41865f4a6_bg.wasm:wasm-function[2707]:0xf4373
maap-9136cacef662140e.wasm.console_error_panic_hook::hook_impl::h5ae464cddaa778d8@http://0.0.0.0:8080/maap-66a67bc41865f4a6_bg.wasm:wasm-function[671]:0x96e46
maap-9136cacef662140e.wasm.console_error_panic_hook::hook::h8d3226f2e8bba476@http://0.0.0.0:8080/maap-66a67bc41865f4a6_bg.wasm:wasm-function[4007]:0x10923a
maap-9136cacef662140e.wasm.core::ops::function::Fn::call::h69a48aa9a25ea0d5@http://0.0.0.0:8080/maap-66a67bc41865f4a6_bg.wasm:wasm-function[3325]:0xff9e8
m…
[maap-66a67bc41865f4a6.js:337:21](http://0.0.0.0:8080/maap-66a67bc41865f4a6.js)
Uncaught RuntimeError: unreachable executed
[maap-66a67bc41865f4a6_bg.wasm:1108023:1](http://0.0.0.0:8080/maap-66a67bc41865f4a6_bg.wasm)

Environment:

  • Yew version: 0.21.0 && master
  • Rust version: 1.83
  • Build tool: trunk
  • OS, if relevant: Ubuntu LTS (latest)
  • Browser and version, if relevant: Firefox 133.0.3 (latest at the time) && Vanadium 131.0.6778.135 (Chromium-based) on Android.

Questionnaire

  • [x] I'm interested in fixing this myself but don't know where to start (and if it's not too much of a rabbit-hole :))
  • [ ] I would like to fix and I have a solution
  • [ ] I don't have time to fix this right now, but maybe later

Thanks for the awesome project by the way, it's a pleasure to do front-end in Rust :100:

lowlevl avatar Dec 17 '24 00:12 lowlevl

EDIT :top: : Minimized the example even more and got rid of the leaflet dependency.

lowlevl avatar Dec 17 '24 16:12 lowlevl

In the meantime, I ended up not using <Suspense> elements altogether and using a combination of use_state and yew::platform::spawn_local.

    let value = use_state(|| Option::<&str>::None);

    use_effect({
        let setter = value.setter();

        || {
            yew::platform::spawn_local(async move {
                // Do async stuff.

                setter.set(Some("super value you got from async stuff"));
            })
        }
    });

    html! {
        <>
            if let Some(value) = value.as_ref() {
                {format!("The async value is {value}")}
            } else {
                {"Loading..."}
            }
        </>
    }

lowlevl avatar Dec 17 '24 17:12 lowlevl