Compare commits

..

1 Commits
4475 ... 4486

Author SHA1 Message Date
Greg Johnston
23529b2e0b fix: use unkeyed paths for all store patching (closes #4486) 2025-12-14 17:12:21 -05:00
25 changed files with 637 additions and 872 deletions

View File

@@ -103,7 +103,7 @@ jobs:
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v5
- uses: actions/cache@v4
if: contains(inputs.directory, 'examples')
name: Setup pnpm cache
with:

979
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -49,45 +49,45 @@ any_spawner = { path = "./any_spawner/", version = "0.3.0" }
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
either_of = { path = "./either_of/", version = "0.1.6" }
hydration_context = { path = "./hydration_context", version = "0.3.0" }
leptos = { path = "./leptos", version = "0.8.15" }
leptos = { path = "./leptos", version = "0.8.14" }
leptos_config = { path = "./leptos_config", version = "0.8.8" }
leptos_dom = { path = "./leptos_dom", version = "0.8.7" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.5" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.7" }
leptos_macro = { path = "./leptos_macro", version = "0.8.13" }
leptos_router = { path = "./router", version = "0.8.11" }
leptos_macro = { path = "./leptos_macro", version = "0.8.12" }
leptos_router = { path = "./router", version = "0.8.10" }
leptos_router_macro = { path = "./router_macro", version = "0.8.6" }
leptos_server = { path = "./leptos_server", version = "0.8.6" }
leptos_meta = { path = "./meta", version = "0.8.5" }
next_tuple = { path = "./next_tuple", version = "0.1.0" }
oco_ref = { path = "./oco", version = "0.2.1" }
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
reactive_graph = { path = "./reactive_graph", version = "0.2.12" }
reactive_stores = { path = "./reactive_stores", version = "0.3.1" }
reactive_graph = { path = "./reactive_graph", version = "0.2.11" }
reactive_stores = { path = "./reactive_stores", version = "0.3.0" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.6" }
server_fn = { path = "./server_fn", version = "0.8.9" }
server_fn = { path = "./server_fn", version = "0.8.8" }
server_fn_macro = { path = "./server_fn_macro", version = "0.8.8" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.5" }
tachys = { path = "./tachys", version = "0.2.11" }
# members deps
async-once-cell = { default-features = false, version = "0.5.4" }
async-once-cell = { default-features = false, version = "0.5.3" }
itertools = { default-features = false, version = "0.14.0" }
convert_case = { default-features = false, version = "0.10.0" }
serde_json = { default-features = false, version = "1.0.145" }
trybuild = { default-features = false, version = "1.0.114" }
typed-builder = { default-features = false, version = "0.23.2" }
typed-builder-macro = { default-features = false, version = "0.23.2" }
convert_case = { default-features = false, version = "0.8.0" }
serde_json = { default-features = false, version = "1.0.143" }
trybuild = { default-features = false, version = "1.0.110" }
typed-builder = { default-features = false, version = "0.22.0" }
typed-builder-macro = { default-features = false, version = "0.22.0" }
thiserror = { default-features = false, version = "2.0.17" }
wasm-bindgen = { default-features = false, version = "0.2.106" }
indexmap = { default-features = false, version = "2.12.1" }
wasm-bindgen = { default-features = false, version = "0.2.100" }
indexmap = { default-features = false, version = "2.11.0" }
rstml = { default-features = false, version = "0.12.1" }
rustc_version = { default-features = false, version = "0.4.1" }
guardian = { default-features = false, version = "1.3.0" }
rustc-hash = { default-features = false, version = "2.1.1" }
actix-web = { default-features = false, version = "4.12.1" }
tracing = { default-features = false, version = "0.1.44" }
slotmap = { default-features = false, version = "1.1.1" }
actix-web = { default-features = false, version = "4.11.0" }
tracing = { default-features = false, version = "0.1.41" }
slotmap = { default-features = false, version = "1.0.7" }
futures = { default-features = false, version = "0.3.31" }
dashmap = { default-features = false, version = "6.1.0" }
pin-project-lite = { default-features = false, version = "0.2.16" }
@@ -97,41 +97,41 @@ html-escape = { default-features = false, version = "0.2.13" }
proc-macro-error2 = { default-features = false, version = "2.0.1" }
const_format = { default-features = false, version = "0.2.35" }
gloo-net = { default-features = false, version = "0.6.0" }
url = { default-features = false, version = "2.5.7" }
tokio = { default-features = false, version = "1.48.0" }
url = { default-features = false, version = "2.5.4" }
tokio = { default-features = false, version = "1.47.1" }
base64 = { default-features = false, version = "0.22.1" }
cfg-if = { default-features = false, version = "1.0.4" }
wasm-bindgen-futures = { default-features = false, version = "0.4.56" }
cfg-if = { default-features = false, version = "1.0.3" }
wasm-bindgen-futures = { default-features = false, version = "0.4.50" }
tower = { default-features = false, version = "0.5.2" }
proc-macro2 = { default-features = false, version = "1.0.103" }
serde = { default-features = false, version = "1.0.228" }
proc-macro2 = { default-features = false, version = "1.0.101" }
serde = { default-features = false, version = "1.0.219" }
parking_lot = { default-features = false, version = "0.12.5" }
axum = { default-features = false, version = "0.8.7" }
axum = { default-features = false, version = "0.8.6" }
serde_qs = { default-features = false, version = "0.15.0" }
syn = { default-features = false, version = "2.0.111" }
syn = { default-features = false, version = "2.0.106" }
xxhash-rust = { default-features = false, version = "0.8.15" }
paste = { default-features = false, version = "1.0.15" }
quote = { default-features = false, version = "1.0.42" }
web-sys = { default-features = false, version = "0.3.83" }
js-sys = { default-features = false, version = "0.3.83" }
rand = { default-features = false, version = "0.9.2" }
serde-lite = { default-features = false, version = "0.5.1" }
quote = { default-features = false, version = "1.0.41" }
web-sys = { default-features = false, version = "0.3.77" }
js-sys = { default-features = false, version = "0.3.77" }
rand = { default-features = false, version = "0.9.1" }
serde-lite = { default-features = false, version = "0.5.0" }
tokio-tungstenite = { default-features = false, version = "0.28.0" }
serial_test = { default-features = false, version = "3.2.0" }
erased = { default-features = false, version = "0.1.2" }
glib = { default-features = false, version = "0.21.5" }
glib = { default-features = false, version = "0.20.12" }
async-trait = { default-features = false, version = "0.1.89" }
linear-map = { default-features = false, version = "1.2.0" }
anyhow = { default-features = false, version = "1.0.100" }
walkdir = { default-features = false, version = "2.5.0" }
actix-ws = { default-features = false, version = "0.3.0" }
tower-http = { default-features = false, version = "0.6.8" }
tower-http = { default-features = false, version = "0.6.4" }
prettyplease = { default-features = false, version = "0.2.37" }
inventory = { default-features = false, version = "0.3.21" }
config = { default-features = false, version = "0.15.19" }
camino = { default-features = false, version = "1.2.2" }
config = { default-features = false, version = "0.15.14" }
camino = { default-features = false, version = "1.2.1" }
ciborium = { default-features = false, version = "0.2.2" }
bitcode = { default-features = false, version = "0.6.9" }
bitcode = { default-features = false, version = "0.6.6" }
multer = { default-features = false, version = "3.1.0" }
leptos-spin-macro = { default-features = false, version = "0.2.0" }
sledgehammer_utils = { default-features = false, version = "0.3.1" }
@@ -139,38 +139,38 @@ sledgehammer_bindgen = { default-features = false, version = "0.6.0" }
wasm-streams = { default-features = false, version = "0.4.2" }
rkyv = { default-features = false, version = "0.8.12" }
temp-env = { default-features = false, version = "0.3.6" }
uuid = { default-features = false, version = "1.19.0" }
bytes = { default-features = false, version = "1.11.0" }
http = { default-features = false, version = "1.4.0" }
regex = { default-features = false, version = "1.12.2" }
uuid = { default-features = false, version = "1.18.0" }
bytes = { default-features = false, version = "1.10.1" }
http = { default-features = false, version = "1.3.1" }
regex = { default-features = false, version = "1.11.3" }
drain_filter_polyfill = { default-features = false, version = "0.1.3" }
tempfile = { default-features = false, version = "3.23.0" }
futures-lite = { default-features = false, version = "2.6.1" }
log = { default-features = false, version = "0.4.29" }
log = { default-features = false, version = "0.4.27" }
percent-encoding = { default-features = false, version = "2.3.2" }
async-executor = { default-features = false, version = "1.13.3" }
const-str = { default-features = false, version = "0.7.1" }
async-executor = { default-features = false, version = "1.13.2" }
const-str = { default-features = false, version = "0.6.4" }
http-body-util = { default-features = false, version = "0.1.3" }
hyper = { default-features = false, version = "1.8.1" }
hyper = { default-features = false, version = "1.7.0" }
postcard = { default-features = false, version = "1.1.3" }
rmp-serde = { default-features = false, version = "1.3.0" }
reqwest = { default-features = false, version = "0.12.26" }
reqwest = { default-features = false, version = "0.12.23" }
tower-layer = { default-features = false, version = "0.3.3" }
attribute-derive = { default-features = false, version = "0.10.5" }
insta = { default-features = false, version = "1.45.0" }
codee = { default-features = false, version = "0.3.5" }
insta = { default-features = false, version = "1.43.1" }
codee = { default-features = false, version = "0.3.0" }
actix-http = { default-features = false, version = "3.11.2" }
wasm-bindgen-test = { default-features = false, version = "0.3.56" }
wasm-bindgen-test = { default-features = false, version = "0.3.50" }
rustversion = { default-features = false, version = "1.0.22" }
getrandom = { default-features = false, version = "0.3.4" }
actix-files = { default-features = false, version = "0.6.9" }
getrandom = { default-features = false, version = "0.3.3" }
actix-files = { default-features = false, version = "0.6.6" }
async-lock = { default-features = false, version = "3.4.1" }
base16 = { default-features = false, version = "0.2.1" }
digest = { default-features = false, version = "0.10.7" }
sha2 = { default-features = false, version = "0.10.9" }
subsecond = { default-features = false, version = "0.7.2" }
dioxus-cli-config = { default-features = false, version = "0.7.2" }
dioxus-devtools = { default-features = false, version = "0.7.2" }
sha2 = { default-features = false, version = "0.10.8" }
subsecond = { default-features = false, version = "0.7.0-rc.0" }
dioxus-cli-config = { default-features = false, version = "0.7.0-rc.0" }
dioxus-devtools = { default-features = false, version = "0.7.0-rc.0" }
wasm_split_helpers = { default-features = false, version = "0.2.0" }
[profile.release]

View File

@@ -17,7 +17,7 @@ leptos_router = { path = "../../router" }
serde = { version = "1.0", features = ["derive"] }
thiserror = "2.0.12"
tokio = { version = "1.39", features = [ "rt-multi-thread", "macros", "time" ], optional = true }
wasm-bindgen = "0.2.106"
wasm-bindgen = "0.2.92"
[features]
hydrate = [

View File

@@ -1,38 +0,0 @@
@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

View File

@@ -15,9 +15,3 @@ pub async fn click_link(client: &Client, text: &str) -> Result<()> {
link.click().await?;
Ok(())
}
pub async fn click_button(client: &Client, id: &str) -> Result<()> {
let btn = find::element_by_id(&client, &id).await?;
btn.click().await?;
Ok(())
}

View File

@@ -7,15 +7,7 @@ pub async fn result_text_is(
client: &Client,
expected_text: &str,
) -> Result<()> {
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?;
let actual = find::text_at_id(client, "result").await?;
assert_eq!(&actual, expected_text);
Ok(())
}

View File

@@ -20,14 +20,6 @@ async fn i_select_the_link(world: &mut AppWorld, text: String) -> Result<()> {
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")]
#[when(expr = "I select the following links")]
async fn i_select_the_following_links(
@@ -62,10 +54,3 @@ async fn i_go_back(world: &mut AppWorld) -> Result<()> {
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(())
}

View File

@@ -19,17 +19,6 @@ async fn i_see_the_result_is_the_string(
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$")]
async fn i_see_the_navbar(world: &mut AppWorld) -> Result<()> {
let client = &world.client;

View File

@@ -1,7 +1,7 @@
use crate::{
issue_4005::Routes4005, issue_4088::Routes4088, issue_4217::Routes4217,
issue_4285::Routes4285, issue_4296::Routes4296, issue_4324::Routes4324,
issue_4492::Routes4492, pr_4015::Routes4015, pr_4091::Routes4091,
pr_4015::Routes4015, pr_4091::Routes4091,
};
use leptos::prelude::*;
use leptos_meta::{MetaTags, *};
@@ -48,7 +48,6 @@ pub fn App() -> impl IntoView {
<Routes4285/>
<Routes4296/>
<Routes4324/>
<Routes4492/>
</Routes>
</main>
</Router>
@@ -76,7 +75,6 @@ fn HomePage() -> impl IntoView {
<li><a href="/4285/">"4285"</a></li>
<li><a href="/4296/">"4296"</a></li>
<li><a href="/4324/">"4324"</a></li>
<li><a href="/4492/">"4492"</a></li>
</ul>
</nav>
}

View File

@@ -1,114 +0,0 @@
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(())
}

View File

@@ -5,7 +5,6 @@ mod issue_4217;
mod issue_4285;
mod issue_4296;
mod issue_4324;
mod issue_4492;
mod pr_4015;
mod pr_4091;

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos"
version = "0.8.15"
version = "0.8.14"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -15,10 +15,7 @@ use reactive_graph::{
effect::RenderEffect,
owner::{provide_context, use_context, Owner},
signal::ArcRwSignal,
traits::{
Dispose, Get, Read, ReadUntracked, Track, With, WithUntracked,
WriteValue,
},
traits::{Dispose, Get, Read, ReadUntracked, Track, With, WriteValue},
};
use slotmap::{DefaultKey, SlotMap};
use std::sync::{Arc, Mutex};
@@ -122,19 +119,14 @@ where
provide_context(SuspenseContext {
tasks: tasks.clone(),
});
let none_pending = ArcMemo::new({
let tasks = tasks.clone();
move |prev: Option<&bool>| {
tasks.track();
if prev.is_none() && starts_local {
false
} else {
tasks.with(SlotMap::is_empty)
}
let none_pending = ArcMemo::new(move |prev: Option<&bool>| {
tasks.track();
if prev.is_none() && starts_local {
false
} else {
tasks.with(SlotMap::is_empty)
}
});
let has_tasks =
Arc::new(move || !tasks.with_untracked(SlotMap::is_empty));
OwnedView::new(SuspenseBoundary::<false, _, _> {
id,
@@ -142,7 +134,6 @@ where
fallback,
children,
error_boundary_parent,
has_tasks,
})
})
}
@@ -165,7 +156,6 @@ pub(crate) struct SuspenseBoundary<const TRANSITION: bool, Fal, Chil> {
pub fallback: Fal,
pub children: Chil,
pub error_boundary_parent: Option<ErrorBoundarySuspendedChildren>,
pub has_tasks: Arc<dyn Fn() -> bool + Send + Sync>,
}
impl<const TRANSITION: bool, Fal, Chil> Render
@@ -202,26 +192,12 @@ where
outer_owner.clone(),
);
let state = if let Some(mut state) = prev {
if let Some(mut state) = prev {
this.rebuild(&mut state);
state
} else {
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
})
}
@@ -259,7 +235,6 @@ where
fallback,
children,
error_boundary_parent,
has_tasks,
} = self;
SuspenseBoundary {
id,
@@ -267,7 +242,6 @@ where
fallback,
children: children.add_any_attr(attr),
error_boundary_parent,
has_tasks,
}
}
}

View File

@@ -10,11 +10,10 @@ use reactive_graph::{
effect::Effect,
owner::{provide_context, use_context, Owner},
signal::ArcRwSignal,
traits::{Get, Set, Track, With, WithUntracked},
traits::{Get, Set, Track, With},
wrappers::write::SignalSetter,
};
use slotmap::{DefaultKey, SlotMap};
use std::sync::Arc;
use tachys::reactive_graph::OwnedView;
/// If any [`Resource`](crate::prelude::Resource) is read in the `children` of this
@@ -105,19 +104,14 @@ where
provide_context(SuspenseContext {
tasks: tasks.clone(),
});
let none_pending = ArcMemo::new({
let tasks = tasks.clone();
move |prev: Option<&bool>| {
tasks.track();
if prev.is_none() && starts_local {
false
} else {
tasks.with(SlotMap::is_empty)
}
let none_pending = ArcMemo::new(move |prev: Option<&bool>| {
tasks.track();
if prev.is_none() && starts_local {
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 {
Effect::new_isomorphic({
let none_pending = none_pending.clone();
@@ -133,7 +127,6 @@ where
fallback,
children,
error_boundary_parent,
has_tasks,
})
})
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_macro"
version = "0.8.14"
version = "0.8.12"
authors = ["Greg Johnston"]
license = "MIT"
repository = "https://github.com/leptos-rs/leptos"

View File

@@ -176,9 +176,7 @@ pub(crate) fn component_to_tokens(
let spreads = (!(spreads.is_empty())).then(|| {
if cfg!(feature = "__internal_erase_components") {
quote! {
.add_any_attr({
vec![#(::leptos::attr::any_attribute::IntoAnyAttribute::into_any_attr(#spreads),)*]
})
.add_any_attr(vec![#(#spreads.into_any_attr(),)*])
}
} else {
quote! {

View File

@@ -15,7 +15,7 @@ use codee::{
Decoder, Encoder,
};
use core::{fmt::Debug, marker::PhantomData};
use futures::{Future, FutureExt};
use futures::Future;
use or_poisoned::OrPoisoned;
use reactive_graph::{
computed::{
@@ -258,17 +258,11 @@ where
if let Some(suspense_context) = use_context::<SuspenseContext>() {
if self.value.read().or_poisoned().is_none() {
let handle = suspense_context.task_id();
let mut ready =
Box::pin(SpecialNonReactiveFuture::new(self.ready()));
match ready.as_mut().now_or_never() {
Some(_) => drop(handle),
None => {
reactive_graph::spawn(async move {
ready.await;
drop(handle);
});
}
}
let ready = SpecialNonReactiveFuture::new(self.ready());
reactive_graph::spawn(async move {
ready.await;
drop(handle);
});
self.suspenses.write().or_poisoned().push(suspense_context);
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_graph"
version = "0.2.12"
version = "0.2.11"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"
@@ -32,7 +32,7 @@ indexmap = { workspace = true, default-features = true }
paste = { workspace = true, default-features = true }
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
web-sys = { version = "0.3.83", features = ["console"] }
web-sys = { version = "0.3.77", features = ["console"] }
[dev-dependencies]
tokio = { features = [

View File

@@ -632,29 +632,12 @@ impl<T: 'static> ReadUntracked for ArcAsyncDerived<T> {
fn try_read_untracked(&self) -> Option<Self::Value> {
if let Some(suspense_context) = use_context::<SuspenseContext>() {
// create a handle to register it with suspense
let handle = suspense_context.task_id();
// check if the task is *already* ready
let mut ready =
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
let ready = SpecialNonReactiveFuture::new(self.ready());
crate::spawn(async move {
ready.await;
drop(handle);
});
self.inner
.write()
.or_poisoned()

View File

@@ -1,6 +1,6 @@
[package]
name = "reactive_stores"
version = "0.3.1"
version = "0.3.0"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"

View File

@@ -140,20 +140,13 @@ where
}
fn track_field(&self) {
let mut full_path = self.path().into_iter().collect::<StorePath>();
let inner = self
.inner
.get_trigger(self.inner.path().into_iter().collect());
inner.this.track();
let trigger = self.get_trigger(self.path().into_iter().collect());
trigger.this.track();
trigger.children.track();
// tracks `this` for all ancestors: i.e., it will track any change that is made
// directly to one of its ancestors, but not a change made to a *child* of an ancestor
// (which would end up with every subfield tracking its own siblings, because they are
// children of its parent)
while !full_path.is_empty() {
full_path.pop();
let inner = self.get_trigger(full_path.clone());
inner.this.track();
}
}
}
@@ -183,7 +176,6 @@ where
{
inner: KeyedSubfield<Inner, Prev, K, T>,
guard: Option<Guard>,
untracked: bool,
}
impl<Inner, Prev, K, T, Guard> Deref
@@ -235,7 +227,6 @@ where
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
{
fn untrack(&mut self) {
self.untracked = true;
if let Some(inner) = self.guard.as_mut() {
inner.untrack();
}
@@ -260,10 +251,7 @@ where
// now that the write lock is release, we can get a read lock to refresh this keyed field
// based on the new value
self.inner.update_keys();
if !self.untracked {
self.inner.notify();
}
self.inner.notify();
// reactive updates happen on the next tick
}
@@ -356,7 +344,6 @@ where
Some(KeyedSubfieldWriteGuard {
inner: self.clone(),
guard: Some(guard),
untracked: false,
})
}
@@ -368,7 +355,6 @@ where
Some(KeyedSubfieldWriteGuard {
inner: self.clone(),
guard: Some(guard),
untracked: true,
})
}
}
@@ -893,41 +879,4 @@ mod tests {
assert_eq!(b_count.load(Ordering::Relaxed), 1);
assert_eq!(c_count.load(Ordering::Relaxed), 1);
}
#[tokio::test]
async fn untracked_write_on_keyed_subfield_shouldnt_notify() {
_ = any_spawner::Executor::init_tokio();
let store = Store::new(data());
assert_eq!(store.read_untracked().todos.len(), 3);
// create an effect to read from the keyed subfield
let todos_count = Arc::new(AtomicUsize::new(0));
Effect::new_sync({
let todos_count = Arc::clone(&todos_count);
move || {
store.todos().track();
todos_count.fetch_add(1, Ordering::Relaxed);
}
});
tick().await;
assert_eq!(todos_count.load(Ordering::Relaxed), 1);
// writing to keyed subfield notifies the iterator
store.todos().write().push(Todo {
id: 13,
label: "D".into(),
});
tick().await;
assert_eq!(todos_count.load(Ordering::Relaxed), 2);
// but an untracked write doesn't
store.todos().write_untracked().push(Todo {
id: 14,
label: "E".into(),
});
tick().await;
assert_eq!(todos_count.load(Ordering::Relaxed), 2);
}
}

View File

@@ -833,7 +833,7 @@ mod tests {
use reactive_graph::{
effect::Effect,
owner::StoredValue,
traits::{Read, ReadUntracked, Set, Track, Update, Write},
traits::{Read, ReadUntracked, Set, Update, Write},
};
use std::sync::{
atomic::{AtomicUsize, Ordering},
@@ -1375,34 +1375,4 @@ mod tests {
assert_eq!(combined_count.load(Ordering::Relaxed), 3);
}
#[tokio::test]
async fn untracked_write_on_subfield_shouldnt_notify() {
_ = any_spawner::Executor::init_tokio();
let name_count = Arc::new(AtomicUsize::new(0));
let store = Store::new(data());
let tracked_field = store.user();
Effect::new_sync({
let name_count = Arc::clone(&name_count);
move |_| {
tracked_field.track();
name_count.fetch_add(1, Ordering::Relaxed);
}
});
tick().await;
assert_eq!(name_count.load(Ordering::Relaxed), 1);
tracked_field.write().push('!');
tick().await;
assert_eq!(name_count.load(Ordering::Relaxed), 2);
tracked_field.write_untracked().push('!');
tick().await;
assert_eq!(name_count.load(Ordering::Relaxed), 2);
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.8.11"
version = "0.8.10"
authors = ["Greg Johnston", "Ben Wishovich"]
license = "MIT"
readme = "../README.md"

View File

@@ -5,7 +5,7 @@ license = "MIT"
repository = "https://github.com/leptos-rs/leptos"
description = "RPC for any web framework."
readme = "../README.md"
version = "0.8.9"
version = "0.8.8"
rust-version.workspace = true
edition.workspace = true
@@ -32,7 +32,7 @@ dashmap = { workspace = true, default-features = true }
## servers
# actix
actix-web = { optional = true, workspace = true, default-features = false, features = ["ws"] }
actix-web = { optional = true, workspace = true, default-features = false }
actix-ws = { optional = true, workspace = true, default-features = true }
# axum