DioxusLabs / dioxus

Fullstack GUI library for web, desktop, mobile, and more.
https://dioxuslabs.com
Apache License 2.0
18.5k stars 704 forks source link

UseEffect only runs on render #2307

Open elias098 opened 2 weeks ago

elias098 commented 2 weeks ago

Problem

Currently use_effect uses wait_for_next_render().await; which means that it only runs on renders.

This is a problem if a signal gets updates without triggering a render.

One such instance is that a queue of updates build up, with one effect running on every render, as below.

Steps To Reproduce

fn App() -> Element {
    let mut signal = use_signal(|| 1);
    use_effect(move || log::log!(log::Level::Info, "{}", signal()));

    let mut eval = use_hook(|| {
        eval(
            r#"
            setInterval(() => {
                console.log("update js");
                dioxus.send("");
            }, 1000)
            "#,
        )
    });

    use_hook(|| {
        spawn(async move {
            loop {
                eval.recv().await.unwrap();
                signal += 1;
            }
        })
    });

    rsx! {
        button {
            onclick: |_| ()
        }
    }
}

Expected behavior

I would expect effects to run any time a used signal updates

Environment:

Questionnaire

elias098 commented 2 weeks ago

If this is intended it should probably be documented. this might also just be me being used to leptos and not understanding dioxus version properly.

marc2332 commented 2 weeks ago

Looks like signals mutated from async tasks do not rerun effects

Doesn't work:

fn app() -> Element {
    let mut count = use_signal(|| 0);

    use_effect(move || {
        println!("{}", count());
    });

    use_hook(move || {
        spawn(async move {
            loop {
                sleep(Duration::from_secs(1)).await;
                count += 1
            }
        })
    });

    rsx!(
        button {
            onclick: move |_| println!("{}", count()),
            "read"
        }
        button {
            onclick: |_| needs_update(),
            "rerun"
        }
    )
}

Works:

fn app() -> Element {
    let mut count = use_signal(|| 0);

    use_effect(move || {
        println!("{}", count);
    });

    println!("rerunning");

    rsx!(
        button {
            onclick: move |_| count += 1,
            "Increase"
        }
        button {
            onclick: |_| needs_update(),
            "rerun"
        }
    )
}