mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 11:04:40 -05:00
fix: do not show Transition fallback on 2nd change if 1st change resolved synchronously (closes #4492, closes #3868) (#4495)
This commit is contained in:
@@ -17,7 +17,7 @@ leptos_router = { path = "../../router" }
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
tokio = { version = "1.39", features = [ "rt-multi-thread", "macros", "time" ], optional = true }
|
tokio = { version = "1.39", features = [ "rt-multi-thread", "macros", "time" ], optional = true }
|
||||||
wasm-bindgen = "0.2.92"
|
wasm-bindgen = "0.2.106"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
hydrate = [
|
hydrate = [
|
||||||
|
|||||||
38
examples/regression/e2e/features/issue_4492.feature
Normal file
38
examples/regression/e2e/features/issue_4492.feature
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
@check_issue_4492
|
||||||
|
Feature: Regression test for issue #4492
|
||||||
|
|
||||||
|
Scenario: Scenario A should show Loading once on first load.
|
||||||
|
Given I see the app
|
||||||
|
And I can access regression test 4492
|
||||||
|
When I click the button a-toggle
|
||||||
|
Then I see a-result has the text Loading...
|
||||||
|
When I wait 100ms
|
||||||
|
Then I see a-result has the text 0
|
||||||
|
When I click the button a-button
|
||||||
|
Then I see a-result has the text 0
|
||||||
|
When I wait 100ms
|
||||||
|
Then I see a-result has the text 1
|
||||||
|
|
||||||
|
Scenario: Scenario B should never show Loading
|
||||||
|
Given I see the app
|
||||||
|
And I can access regression test 4492
|
||||||
|
When I click the button b-toggle
|
||||||
|
Then I see b-result has the text 0
|
||||||
|
When I click the button b-button
|
||||||
|
Then I see b-result has the text 0
|
||||||
|
When I wait 100ms
|
||||||
|
Then I see b-result has the text 1
|
||||||
|
When I click the button b-button
|
||||||
|
Then I see b-result has the text 1
|
||||||
|
When I wait 100ms
|
||||||
|
Then I see b-result has the text 2
|
||||||
|
|
||||||
|
Scenario: Scenario C should never show Loading
|
||||||
|
Given I see the app
|
||||||
|
And I can access regression test 4492
|
||||||
|
When I click the button c-toggle
|
||||||
|
Then I see c-result has the text 0
|
||||||
|
When I click the button c-button
|
||||||
|
Then I see c-result has the text 42
|
||||||
|
When I wait 100ms
|
||||||
|
Then I see c-result has the text 1
|
||||||
@@ -15,3 +15,9 @@ pub async fn click_link(client: &Client, text: &str) -> Result<()> {
|
|||||||
link.click().await?;
|
link.click().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn click_button(client: &Client, id: &str) -> Result<()> {
|
||||||
|
let btn = find::element_by_id(&client, &id).await?;
|
||||||
|
btn.click().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
10
examples/regression/e2e/tests/fixtures/check.rs
vendored
10
examples/regression/e2e/tests/fixtures/check.rs
vendored
@@ -7,7 +7,15 @@ pub async fn result_text_is(
|
|||||||
client: &Client,
|
client: &Client,
|
||||||
expected_text: &str,
|
expected_text: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let actual = find::text_at_id(client, "result").await?;
|
element_text_is(client, "result", expected_text).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn element_text_is(
|
||||||
|
client: &Client,
|
||||||
|
id: &str,
|
||||||
|
expected_text: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let actual = find::text_at_id(client, id).await?;
|
||||||
assert_eq!(&actual, expected_text);
|
assert_eq!(&actual, expected_text);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ async fn i_select_the_link(world: &mut AppWorld, text: String) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[when(regex = "^I click the button (.*)$")]
|
||||||
|
async fn i_click_the_button(world: &mut AppWorld, id: String) -> Result<()> {
|
||||||
|
let client = &world.client;
|
||||||
|
action::click_button(client, &id).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[given(expr = "I select the following links")]
|
#[given(expr = "I select the following links")]
|
||||||
#[when(expr = "I select the following links")]
|
#[when(expr = "I select the following links")]
|
||||||
async fn i_select_the_following_links(
|
async fn i_select_the_following_links(
|
||||||
@@ -54,3 +62,10 @@ async fn i_go_back(world: &mut AppWorld) -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[when(regex = r"^I wait (\d+)ms$")]
|
||||||
|
async fn i_wait_ms(_world: &mut AppWorld, ms: u64) -> Result<()> {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,17 @@ async fn i_see_the_result_is_the_string(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[then(regex = r"^I see ([\w-]+) has the text (.*)$")]
|
||||||
|
async fn i_see_element_has_text(
|
||||||
|
world: &mut AppWorld,
|
||||||
|
id: String,
|
||||||
|
text: String,
|
||||||
|
) -> Result<()> {
|
||||||
|
let client = &world.client;
|
||||||
|
check::element_text_is(client, &id, &text).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[then(regex = r"^I see the navbar$")]
|
#[then(regex = r"^I see the navbar$")]
|
||||||
async fn i_see_the_navbar(world: &mut AppWorld) -> Result<()> {
|
async fn i_see_the_navbar(world: &mut AppWorld) -> Result<()> {
|
||||||
let client = &world.client;
|
let client = &world.client;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
issue_4005::Routes4005, issue_4088::Routes4088, issue_4217::Routes4217,
|
issue_4005::Routes4005, issue_4088::Routes4088, issue_4217::Routes4217,
|
||||||
issue_4285::Routes4285, issue_4296::Routes4296, issue_4324::Routes4324,
|
issue_4285::Routes4285, issue_4296::Routes4296, issue_4324::Routes4324,
|
||||||
pr_4015::Routes4015, pr_4091::Routes4091,
|
issue_4492::Routes4492, pr_4015::Routes4015, pr_4091::Routes4091,
|
||||||
};
|
};
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_meta::{MetaTags, *};
|
use leptos_meta::{MetaTags, *};
|
||||||
@@ -48,6 +48,7 @@ pub fn App() -> impl IntoView {
|
|||||||
<Routes4285/>
|
<Routes4285/>
|
||||||
<Routes4296/>
|
<Routes4296/>
|
||||||
<Routes4324/>
|
<Routes4324/>
|
||||||
|
<Routes4492/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
</Router>
|
</Router>
|
||||||
@@ -75,6 +76,7 @@ fn HomePage() -> impl IntoView {
|
|||||||
<li><a href="/4285/">"4285"</a></li>
|
<li><a href="/4285/">"4285"</a></li>
|
||||||
<li><a href="/4296/">"4296"</a></li>
|
<li><a href="/4296/">"4296"</a></li>
|
||||||
<li><a href="/4324/">"4324"</a></li>
|
<li><a href="/4324/">"4324"</a></li>
|
||||||
|
<li><a href="/4492/">"4492"</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
}
|
}
|
||||||
|
|||||||
114
examples/regression/src/issue_4492.rs
Normal file
114
examples/regression/src/issue_4492.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use leptos_router::{
|
||||||
|
components::Route, path, MatchNestedRoutes, NavigateOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Routes4492() -> impl MatchNestedRoutes + Clone {
|
||||||
|
view! {
|
||||||
|
<Route path=path!("4492") view=Issue4492/>
|
||||||
|
}
|
||||||
|
.into_inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn Issue4492() -> impl IntoView {
|
||||||
|
let show_a = RwSignal::new(false);
|
||||||
|
let show_b = RwSignal::new(false);
|
||||||
|
let show_c = RwSignal::new(false);
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<button id="a-toggle" on:click=move |_| show_a.set(!show_a.get())>"Toggle A"</button>
|
||||||
|
<button id="b-toggle" on:click=move |_| show_b.set(!show_b.get())>"Toggle B"</button>
|
||||||
|
<button id="c-toggle" on:click=move |_| show_c.set(!show_c.get())>"Toggle C"</button>
|
||||||
|
|
||||||
|
<Show when=move || show_a.get()>
|
||||||
|
<ScenarioA/>
|
||||||
|
</Show>
|
||||||
|
<Show when=move || show_b.get()>
|
||||||
|
<ScenarioB/>
|
||||||
|
</Show>
|
||||||
|
<Show when=move || show_c.get()>
|
||||||
|
<ScenarioC/>
|
||||||
|
</Show>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn ScenarioA() -> impl IntoView {
|
||||||
|
// scenario A: one truly-async resource is read on click
|
||||||
|
let counter = RwSignal::new(0);
|
||||||
|
let resource = Resource::new(
|
||||||
|
move || counter.get(),
|
||||||
|
|count| async move {
|
||||||
|
sleep(50).await.unwrap();
|
||||||
|
count
|
||||||
|
},
|
||||||
|
);
|
||||||
|
view! {
|
||||||
|
<Transition fallback=|| view! { <p id="a-result">"Loading..."</p> }>
|
||||||
|
<p id="a-result">{resource}</p>
|
||||||
|
</Transition>
|
||||||
|
<button id="a-button" on:click=move |_| *counter.write() += 1>"+1"</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn ScenarioB() -> impl IntoView {
|
||||||
|
// scenario B: resource immediately available first time, then after 250ms
|
||||||
|
let counter = RwSignal::new(0);
|
||||||
|
let resource = Resource::new(
|
||||||
|
move || counter.get(),
|
||||||
|
|count| async move {
|
||||||
|
if count == 0 {
|
||||||
|
count
|
||||||
|
} else {
|
||||||
|
sleep(50).await.unwrap();
|
||||||
|
count
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
view! {
|
||||||
|
<Transition fallback=|| view! { <p id="b-result">"Loading..."</p> }>
|
||||||
|
<p id="b-result">{resource}</p>
|
||||||
|
</Transition>
|
||||||
|
<button id="b-button" on:click=move |_| *counter.write() += 1>"+1"</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn ScenarioC() -> impl IntoView {
|
||||||
|
// scenario C: not even a resource on the first run, just a value
|
||||||
|
// see https://github.com/leptos-rs/leptos/issues/3868
|
||||||
|
let counter = RwSignal::new(0);
|
||||||
|
let s_res = StoredValue::new(None::<ArcLocalResource<i32>>);
|
||||||
|
let resource = move || {
|
||||||
|
let count = counter.get();
|
||||||
|
if count == 0 {
|
||||||
|
count
|
||||||
|
} else {
|
||||||
|
let r = s_res.get_value().unwrap_or_else(|| {
|
||||||
|
let res = ArcLocalResource::new(move || async move {
|
||||||
|
sleep(50).await.unwrap();
|
||||||
|
count
|
||||||
|
});
|
||||||
|
s_res.set_value(Some(res.clone()));
|
||||||
|
res
|
||||||
|
});
|
||||||
|
r.get().unwrap_or(42)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<Transition fallback=|| view! { <p id="c-result">"Loading..."</p> }>
|
||||||
|
<p id="c-result">{resource}</p>
|
||||||
|
</Transition>
|
||||||
|
<button id="c-button" on:click=move |_| *counter.write() += 1>"+1"</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
async fn sleep(ms: u64) -> Result<(), ServerFnError> {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ mod issue_4217;
|
|||||||
mod issue_4285;
|
mod issue_4285;
|
||||||
mod issue_4296;
|
mod issue_4296;
|
||||||
mod issue_4324;
|
mod issue_4324;
|
||||||
|
mod issue_4492;
|
||||||
mod pr_4015;
|
mod pr_4015;
|
||||||
mod pr_4091;
|
mod pr_4091;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ use reactive_graph::{
|
|||||||
effect::RenderEffect,
|
effect::RenderEffect,
|
||||||
owner::{provide_context, use_context, Owner},
|
owner::{provide_context, use_context, Owner},
|
||||||
signal::ArcRwSignal,
|
signal::ArcRwSignal,
|
||||||
traits::{Dispose, Get, Read, ReadUntracked, Track, With, WriteValue},
|
traits::{
|
||||||
|
Dispose, Get, Read, ReadUntracked, Track, With, WithUntracked,
|
||||||
|
WriteValue,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use slotmap::{DefaultKey, SlotMap};
|
use slotmap::{DefaultKey, SlotMap};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@@ -119,14 +122,19 @@ where
|
|||||||
provide_context(SuspenseContext {
|
provide_context(SuspenseContext {
|
||||||
tasks: tasks.clone(),
|
tasks: tasks.clone(),
|
||||||
});
|
});
|
||||||
let none_pending = ArcMemo::new(move |prev: Option<&bool>| {
|
let none_pending = ArcMemo::new({
|
||||||
tasks.track();
|
let tasks = tasks.clone();
|
||||||
if prev.is_none() && starts_local {
|
move |prev: Option<&bool>| {
|
||||||
false
|
tasks.track();
|
||||||
} else {
|
if prev.is_none() && starts_local {
|
||||||
tasks.with(SlotMap::is_empty)
|
false
|
||||||
|
} else {
|
||||||
|
tasks.with(SlotMap::is_empty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let has_tasks =
|
||||||
|
Arc::new(move || !tasks.with_untracked(SlotMap::is_empty));
|
||||||
|
|
||||||
OwnedView::new(SuspenseBoundary::<false, _, _> {
|
OwnedView::new(SuspenseBoundary::<false, _, _> {
|
||||||
id,
|
id,
|
||||||
@@ -134,6 +142,7 @@ where
|
|||||||
fallback,
|
fallback,
|
||||||
children,
|
children,
|
||||||
error_boundary_parent,
|
error_boundary_parent,
|
||||||
|
has_tasks,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -156,6 +165,7 @@ pub(crate) struct SuspenseBoundary<const TRANSITION: bool, Fal, Chil> {
|
|||||||
pub fallback: Fal,
|
pub fallback: Fal,
|
||||||
pub children: Chil,
|
pub children: Chil,
|
||||||
pub error_boundary_parent: Option<ErrorBoundarySuspendedChildren>,
|
pub error_boundary_parent: Option<ErrorBoundarySuspendedChildren>,
|
||||||
|
pub has_tasks: Arc<dyn Fn() -> bool + Send + Sync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const TRANSITION: bool, Fal, Chil> Render
|
impl<const TRANSITION: bool, Fal, Chil> Render
|
||||||
@@ -192,12 +202,26 @@ where
|
|||||||
outer_owner.clone(),
|
outer_owner.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(mut state) = prev {
|
let state = if let Some(mut state) = prev {
|
||||||
this.rebuild(&mut state);
|
this.rebuild(&mut state);
|
||||||
state
|
state
|
||||||
} else {
|
} else {
|
||||||
this.build()
|
this.build()
|
||||||
|
};
|
||||||
|
|
||||||
|
if nth_run == 1 && !(self.has_tasks)() {
|
||||||
|
// if this is the first run, and there are no pending resources at this point,
|
||||||
|
// it means that there were no actually-async resources read while rendering the children
|
||||||
|
// this means that we're effectively on the settled second run: none_pending
|
||||||
|
// won't change false => true and cause this to rerender (and therefore increment nth_run)
|
||||||
|
//
|
||||||
|
// we increment it manually here so that future resource changes won't cause the transition fallback
|
||||||
|
// to be displayed for the first time
|
||||||
|
// see https://github.com/leptos-rs/leptos/issues/3868, https://github.com/leptos-rs/leptos/issues/4492
|
||||||
|
nth_run += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +259,7 @@ where
|
|||||||
fallback,
|
fallback,
|
||||||
children,
|
children,
|
||||||
error_boundary_parent,
|
error_boundary_parent,
|
||||||
|
has_tasks,
|
||||||
} = self;
|
} = self;
|
||||||
SuspenseBoundary {
|
SuspenseBoundary {
|
||||||
id,
|
id,
|
||||||
@@ -242,6 +267,7 @@ where
|
|||||||
fallback,
|
fallback,
|
||||||
children: children.add_any_attr(attr),
|
children: children.add_any_attr(attr),
|
||||||
error_boundary_parent,
|
error_boundary_parent,
|
||||||
|
has_tasks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ use reactive_graph::{
|
|||||||
effect::Effect,
|
effect::Effect,
|
||||||
owner::{provide_context, use_context, Owner},
|
owner::{provide_context, use_context, Owner},
|
||||||
signal::ArcRwSignal,
|
signal::ArcRwSignal,
|
||||||
traits::{Get, Set, Track, With},
|
traits::{Get, Set, Track, With, WithUntracked},
|
||||||
wrappers::write::SignalSetter,
|
wrappers::write::SignalSetter,
|
||||||
};
|
};
|
||||||
use slotmap::{DefaultKey, SlotMap};
|
use slotmap::{DefaultKey, SlotMap};
|
||||||
|
use std::sync::Arc;
|
||||||
use tachys::reactive_graph::OwnedView;
|
use tachys::reactive_graph::OwnedView;
|
||||||
|
|
||||||
/// If any [`Resource`](crate::prelude::Resource) is read in the `children` of this
|
/// If any [`Resource`](crate::prelude::Resource) is read in the `children` of this
|
||||||
@@ -104,14 +105,19 @@ where
|
|||||||
provide_context(SuspenseContext {
|
provide_context(SuspenseContext {
|
||||||
tasks: tasks.clone(),
|
tasks: tasks.clone(),
|
||||||
});
|
});
|
||||||
let none_pending = ArcMemo::new(move |prev: Option<&bool>| {
|
let none_pending = ArcMemo::new({
|
||||||
tasks.track();
|
let tasks = tasks.clone();
|
||||||
if prev.is_none() && starts_local {
|
move |prev: Option<&bool>| {
|
||||||
false
|
tasks.track();
|
||||||
} else {
|
if prev.is_none() && starts_local {
|
||||||
tasks.with(SlotMap::is_empty)
|
false
|
||||||
|
} else {
|
||||||
|
tasks.with(SlotMap::is_empty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let has_tasks =
|
||||||
|
Arc::new(move || !tasks.with_untracked(SlotMap::is_empty));
|
||||||
if let Some(set_pending) = set_pending {
|
if let Some(set_pending) = set_pending {
|
||||||
Effect::new_isomorphic({
|
Effect::new_isomorphic({
|
||||||
let none_pending = none_pending.clone();
|
let none_pending = none_pending.clone();
|
||||||
@@ -127,6 +133,7 @@ where
|
|||||||
fallback,
|
fallback,
|
||||||
children,
|
children,
|
||||||
error_boundary_parent,
|
error_boundary_parent,
|
||||||
|
has_tasks,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use codee::{
|
|||||||
Decoder, Encoder,
|
Decoder, Encoder,
|
||||||
};
|
};
|
||||||
use core::{fmt::Debug, marker::PhantomData};
|
use core::{fmt::Debug, marker::PhantomData};
|
||||||
use futures::Future;
|
use futures::{Future, FutureExt};
|
||||||
use or_poisoned::OrPoisoned;
|
use or_poisoned::OrPoisoned;
|
||||||
use reactive_graph::{
|
use reactive_graph::{
|
||||||
computed::{
|
computed::{
|
||||||
@@ -258,11 +258,17 @@ where
|
|||||||
if let Some(suspense_context) = use_context::<SuspenseContext>() {
|
if let Some(suspense_context) = use_context::<SuspenseContext>() {
|
||||||
if self.value.read().or_poisoned().is_none() {
|
if self.value.read().or_poisoned().is_none() {
|
||||||
let handle = suspense_context.task_id();
|
let handle = suspense_context.task_id();
|
||||||
let ready = SpecialNonReactiveFuture::new(self.ready());
|
let mut ready =
|
||||||
reactive_graph::spawn(async move {
|
Box::pin(SpecialNonReactiveFuture::new(self.ready()));
|
||||||
ready.await;
|
match ready.as_mut().now_or_never() {
|
||||||
drop(handle);
|
Some(_) => drop(handle),
|
||||||
});
|
None => {
|
||||||
|
reactive_graph::spawn(async move {
|
||||||
|
ready.await;
|
||||||
|
drop(handle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
self.suspenses.write().or_poisoned().push(suspense_context);
|
self.suspenses.write().or_poisoned().push(suspense_context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -632,12 +632,29 @@ impl<T: 'static> ReadUntracked for ArcAsyncDerived<T> {
|
|||||||
|
|
||||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||||
if let Some(suspense_context) = use_context::<SuspenseContext>() {
|
if let Some(suspense_context) = use_context::<SuspenseContext>() {
|
||||||
|
// create a handle to register it with suspense
|
||||||
let handle = suspense_context.task_id();
|
let handle = suspense_context.task_id();
|
||||||
let ready = SpecialNonReactiveFuture::new(self.ready());
|
|
||||||
crate::spawn(async move {
|
// check if the task is *already* ready
|
||||||
ready.await;
|
let mut ready =
|
||||||
drop(handle);
|
Box::pin(SpecialNonReactiveFuture::new(self.ready()));
|
||||||
});
|
match ready.as_mut().now_or_never() {
|
||||||
|
Some(_) => {
|
||||||
|
// if it's already ready, drop the handle immediately
|
||||||
|
// this will immediately notify the suspense context that it's complete
|
||||||
|
drop(handle);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// otherwise, spawn a task to wait for it to be ready, then drop the handle,
|
||||||
|
// which will notify the suspense
|
||||||
|
crate::spawn(async move {
|
||||||
|
ready.await;
|
||||||
|
drop(handle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the suspense context with our list of them, to be notified later if this re-runs
|
||||||
self.inner
|
self.inner
|
||||||
.write()
|
.write()
|
||||||
.or_poisoned()
|
.or_poisoned()
|
||||||
|
|||||||
Reference in New Issue
Block a user