dioxus
dioxus copied to clipboard
use_effect + signals = footgun
Hi,
the following code doesn't work.
pub fn App(cx: Scope) -> Element {
let foo = use_signal(cx, || 0);
println!("Rendering App {:?}", foo.read());
use_effect(cx, &(foo, ), |(foo, )| async move {
println!("Effect: {:?}", foo);
});
use_on_create(cx, || async move {
sleep(std::time::Duration::from_secs(1)).await;
foo.set(1);
});
render! {
"Hello {foo}"
}
}
I don't know terribly much about how memoization works exactly... but what I think happened with my limited understanding:
- The hook runs the first time, and caches the dependency. (Which is the signal itself.)
- The signal's value is changed; this also updates the value of the signal held by the hook.
- The second time the hook runs, the signal is compared with the one held in the hook... But that one got updated in the background, so we basically the hook checks the signal's current value with the current value, and the effect doesn't fire.
This is... a bit of a footgun, I think. Would it make sense to introduce a marker trait for dependencies, rather than just using a blanket implementation for anything that is Clone+PartialEq?
Signals are not compatible with all hooks in dioxus-hooks
currently. The dioxus-signals
crate defines a different use_effect hook for signals.
Signals track reads instead of memorization which means you don't need to add them to the dependency tuple, but until signals are integrated into dioxus-hooks, you will need to either:
- use the dioxus-signals version of
use_effect
pub fn App(cx: Scope) -> Element {
let foo = use_signal(cx, || 0);
println!("Rendering App {:?}", foo.read());
dioxus_signals::use_effect(cx, move || {
println!("Effect: {:?}", foo);
});
use_on_create(cx, || async move {
sleep(std::time::Duration::from_secs(1)).await;
foo.set(1);
});
render! {
"Hello {foo}"
}
}
- Or add the read cloned value as a dependancy
pub fn App(cx: Scope) -> Element {
let foo = use_signal(cx, || 0);
println!("Rendering App {:?}", foo.read());
use_effect(cx, &(foo.read().clone(), ), |(foo, )| async move {
println!("Effect: {:?}", foo);
});
use_on_create(cx, || async move {
sleep(std::time::Duration::from_secs(1)).await;
foo.set(1);
});
render! {
"Hello {foo}"
}
}
In https://github.com/DioxusLabs/dioxus/pull/1676, any time a signal reads inside an effect, it should add itself as a dependency of that effect which would your original code or this code work:
pub fn App(cx: Scope) -> Element {
let foo = use_signal(cx, || 0);
println!("Rendering App {:?}", foo.read());
use_effect!(cx, || async move {
println!("Effect: {:?}", foo);
});
use_on_create(cx, || async move {
sleep(std::time::Duration::from_secs(1)).await;
foo.set(1);
});
render! {
"Hello {foo}"
}
}
Signals are now the default for effects which fixes this issue