From a9ce60843353299c44827ac340f6092ec3bab93a Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Mon, 13 Jan 2025 21:32:08 -0500 Subject: [PATCH] fix: do not use stale values in AsyncDerived if it is mutated before running (#3475) --- leptos_macro/src/view/mod.rs | 2 +- .../async_derived/arc_async_derived.rs | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/leptos_macro/src/view/mod.rs b/leptos_macro/src/view/mod.rs index 02428d5283..0ea12cdd92 100644 --- a/leptos_macro/src/view/mod.rs +++ b/leptos_macro/src/view/mod.rs @@ -1651,7 +1651,7 @@ pub(crate) fn ident_from_tag_name(tag_name: &NodeName) -> Ident { .path .segments .iter() - .last() + .next_back() .map(|segment| segment.ident.clone()) .expect("element needs to have a name"), NodeName::Block(_) => { diff --git a/reactive_graph/src/computed/async_derived/arc_async_derived.rs b/reactive_graph/src/computed/async_derived/arc_async_derived.rs index 93a8672946..77079d0f60 100644 --- a/reactive_graph/src/computed/async_derived/arc_async_derived.rs +++ b/reactive_graph/src/computed/async_derived/arc_async_derived.rs @@ -272,7 +272,10 @@ macro_rules! spawn_derived { // so that the correct value is set synchronously let initial = initial_fut.as_mut().now_or_never(); match initial { - None => (false, Some(initial_fut)), + None => { + inner.write().or_poisoned().notifier.notify(); + (false, Some(initial_fut)) + } Some(orig_value) => { let mut guard = this.inner.write().or_poisoned(); @@ -296,10 +299,6 @@ macro_rules! spawn_derived { if was_ready { first_run.take(); } - // begin loading eagerly but asynchronously, if not already loaded - if !was_ready { - any_subscriber.mark_dirty(); - } if let Some(source) = $source { any_subscriber.with_observer(|| source.track()); @@ -312,6 +311,18 @@ macro_rules! spawn_derived { let wakers = Arc::downgrade(&this.wakers); let loading = Arc::downgrade(&this.loading); let fut = async move { + // if the AsyncDerived has *already* been marked dirty (i.e., one of its + // sources has changed after creation), we should throw out the Future + // we already created, because its values might be stale + let already_dirty = inner.upgrade() + .as_ref() + .and_then(|inner| inner.read().ok()) + .map(|inner| inner.state == AsyncDerivedState::Dirty) + .unwrap_or(false); + if already_dirty { + initial_fut.take(); + } + while rx.next().await.is_some() { let update_if_necessary = if $should_track { any_subscriber