From bef4d0dd3bbc01074ebf40829ac989f160b44a21 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 25 Aug 2023 15:58:19 -0400 Subject: [PATCH 1/6] fix: resource loading signal pattern for subsequent hydration page loads --- leptos_reactive/src/hydration.rs | 11 +++++-- leptos_reactive/src/resource.rs | 51 +++++++++++++++++++------------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/leptos_reactive/src/hydration.rs b/leptos_reactive/src/hydration.rs index 8eda3e436..8d454bd81 100644 --- a/leptos_reactive/src/hydration.rs +++ b/leptos_reactive/src/hydration.rs @@ -9,6 +9,8 @@ use std::collections::{HashMap, HashSet, VecDeque}; /// Hydration data and other context that is shared between the server /// and the client. pub struct SharedContext { + /// Resources that initially needed to resolve from the server. + pub server_resources: HashSet, /// Resources that have not yet resolved. pub pending_resources: HashSet, /// Resources that have already resolved. @@ -201,24 +203,27 @@ impl Default for SharedContext { let pending_resources: HashSet = pending_resources .map_err(|_| ()) .and_then(|pr| serde_wasm_bindgen::from_value(pr).map_err(|_| ())) - .unwrap_or_default(); + .unwrap(); let resolved_resources = js_sys::Reflect::get( &web_sys::window().unwrap(), &wasm_bindgen::JsValue::from_str("__LEPTOS_RESOLVED_RESOURCES"), ) - .unwrap_or(wasm_bindgen::JsValue::NULL); + .unwrap(); // unwrap_or(wasm_bindgen::JsValue::NULL); let resolved_resources = - serde_wasm_bindgen::from_value(resolved_resources).unwrap_or_default(); + serde_wasm_bindgen::from_value(resolved_resources).unwrap(); + Self { + server_resources: pending_resources.clone(), pending_resources, resolved_resources, pending_fragments: Default::default(), } } else { Self { + server_resources: Default::default(), pending_resources: Default::default(), resolved_resources: Default::default(), pending_fragments: Default::default(), diff --git a/leptos_reactive/src/resource.rs b/leptos_reactive/src/resource.rs index f948bdb8c..1a5b8a684 100644 --- a/leptos_reactive/src/resource.rs +++ b/leptos_reactive/src/resource.rs @@ -504,10 +504,22 @@ where instrument(level = "trace", skip_all,) )] pub fn loading(&self) -> Signal { - let loading = with_runtime(|runtime| { - runtime.resource(self.id, |resource: &ResourceState| { - resource.loading - }) + #[allow(unused_variables)] + let (loading, is_from_server) = with_runtime(|runtime| { + let loading = runtime + .resource(self.id, |resource: &ResourceState| { + resource.loading + }); + #[cfg(feature = "hydrate")] + let is_from_server = runtime + .shared_context + .borrow() + .server_resources + .contains(&self.id); + + #[cfg(not(feature = "hydrate"))] + let is_from_server = false; + (loading, is_from_server) }) .expect( "tried to call Resource::loading() in a runtime that has already \ @@ -519,24 +531,20 @@ where // if the loading signal is read outside Suspense // in hydrate mode, there will be a mismatch on first render // unless we delay a tick - if use_context::().is_none() - && !loading.get_untracked() - { - let (initial, set_initial) = create_signal(true); - queue_microtask(move || set_initial.set(false)); - Signal::derive(move || { - if initial.get() - && use_context::().is_none() - { - true - } else { - loading.get() - } - }) - } else { - loading.into() - } + let (initial, set_initial) = create_signal(true); + queue_microtask(move || set_initial.set(false)); + Signal::derive(move || { + if is_from_server + && initial.get() + && use_context::().is_none() + { + true + } else { + loading.get() + } + }) } + #[cfg(not(feature = "hydrate"))] { loading.into() @@ -785,6 +793,7 @@ where ) )] #[inline(always)] + #[track_caller] fn try_get(&self) -> Option> { let location = std::panic::Location::caller(); with_runtime(|runtime| { From b8098e7992265c291c49293796668d5a14c9d5ee Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 25 Aug 2023 15:59:47 -0400 Subject: [PATCH 2/6] fix: `` fallback on non-initial page loads --- leptos/src/transition.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/leptos/src/transition.rs b/leptos/src/transition.rs index 56c5938e8..c40c45859 100644 --- a/leptos/src/transition.rs +++ b/leptos/src/transition.rs @@ -130,10 +130,7 @@ where if is_first_run(&first_run, &suspense_context) { let has_local_only = suspense_context.has_local_only() || cfg!(feature = "csr"); - if (!has_local_only || child_runs.get() > 0) - && (cfg!(feature = "csr") - || HydrationCtx::is_hydrating()) - { + if (!has_local_only || child_runs.get() > 0) { first_run.set(false); } } From 9adae32847a5ff8d42c541be38590690404dafc7 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 25 Aug 2023 16:00:01 -0400 Subject: [PATCH 3/6] examples: improve hackernews behavior --- examples/hackernews/src/routes/stories.rs | 24 +++--- examples/hackernews/src/routes/story.rs | 77 +++++++++---------- .../hackernews_axum/src/routes/stories.rs | 25 +++--- examples/hackernews_axum/src/routes/story.rs | 77 +++++++++---------- 4 files changed, 94 insertions(+), 109 deletions(-) diff --git a/examples/hackernews/src/routes/stories.rs b/examples/hackernews/src/routes/stories.rs index 1594bddbe..b0a7f00a8 100644 --- a/examples/hackernews/src/routes/stories.rs +++ b/examples/hackernews/src/routes/stories.rs @@ -36,8 +36,8 @@ pub fn Stories() -> impl IntoView { let (pending, set_pending) = create_signal(false); let hide_more_link = move || { - pending() - || stories.get().unwrap_or(None).unwrap_or_default().len() < 28 + stories.get().unwrap_or(None).unwrap_or_default().len() < 28 + || pending() }; view! { @@ -65,20 +65,16 @@ pub fn Stories() -> impl IntoView { }} "page " {page} - "Loading..."

} + - - - "more >" - - -
+ "more >" + +
diff --git a/examples/hackernews/src/routes/story.rs b/examples/hackernews/src/routes/story.rs index daaffa4c9..313d024f6 100644 --- a/examples/hackernews/src/routes/story.rs +++ b/examples/hackernews/src/routes/story.rs @@ -25,48 +25,45 @@ pub fn Story() -> impl IntoView { }; view! { - <> + - - {move || story.get().map(|story| match story { - None => view! {
"Error loading this story."
}, - Some(story) => view! { -
-
- -

{story.title}

-
- - "("{story.domain}")" - - {story.user.map(|user| view! {

- {story.points} - " points | by " - {user.clone()} - {format!(" {}", story.time_ago)} -

})} -
-
-

- {if story.comments_count.unwrap_or_default() > 0 { - format!("{} comments", story.comments_count.unwrap_or_default()) - } else { - "No comments yet.".into() - }} -

-
    - } - /> -
-
+ {move || story.get().map(|story| match story { + None => view! {
"Error loading this story."
}, + Some(story) => view! { +
+
+ +

{story.title}

+
+ + "("{story.domain}")" + + {story.user.map(|user| view! {

+ {story.points} + " points | by " + {user.clone()} + {format!(" {}", story.time_ago)} +

})}
- }}) - } - - +
+

+ {if story.comments_count.unwrap_or_default() > 0 { + format!("{} comments", story.comments_count.unwrap_or_default()) + } else { + "No comments yet.".into() + }} +

+
    + } + /> +
+
+
+ }})} + } } diff --git a/examples/hackernews_axum/src/routes/stories.rs b/examples/hackernews_axum/src/routes/stories.rs index d9f0409f0..09ba7ed11 100644 --- a/examples/hackernews_axum/src/routes/stories.rs +++ b/examples/hackernews_axum/src/routes/stories.rs @@ -36,12 +36,11 @@ pub fn Stories() -> impl IntoView { let (pending, set_pending) = create_signal(false); let hide_more_link = move || { - pending() - || stories.get().unwrap_or(None).unwrap_or_default().len() < 28 + stories.get().unwrap_or(None).unwrap_or_default().len() < 28 + || pending() }; view! { -
@@ -65,20 +64,16 @@ pub fn Stories() -> impl IntoView { }} "page " {page} - "Loading..."

} + - - - "more >" - - -
+ "more >" + +
diff --git a/examples/hackernews_axum/src/routes/story.rs b/examples/hackernews_axum/src/routes/story.rs index 8e0b3e84e..edeaa9073 100644 --- a/examples/hackernews_axum/src/routes/story.rs +++ b/examples/hackernews_axum/src/routes/story.rs @@ -25,48 +25,45 @@ pub fn Story() -> impl IntoView { }; view! { - <> + - - {move || story.get().map(|story| match story { - None => view! {
"Error loading this story."
}, - Some(story) => view! { -
-
- -

{story.title}

-
- - "("{story.domain}")" - - {story.user.map(|user| view! {

- {story.points} - " points | by " - {user.clone()} - {format!(" {}", story.time_ago)} -

})} -
-
-

- {if story.comments_count.unwrap_or_default() > 0 { - format!("{} comments", story.comments_count.unwrap_or_default()) - } else { - "No comments yet.".into() - }} -

-
    - } - /> -
-
+ {move || story.get().map(|story| match story { + None => view! {
"Error loading this story."
}, + Some(story) => view! { +
+
+ +

{story.title}

+
+ + "("{story.domain}")" + + {story.user.map(|user| view! {

+ {story.points} + " points | by " + {user.clone()} + {format!(" {}", story.time_ago)} +

})}
- }}) - } - - +
+

+ {if story.comments_count.unwrap_or_default() > 0 { + format!("{} comments", story.comments_count.unwrap_or_default()) + } else { + "No comments yet.".into() + }} +

+
    + } + /> +
+
+
+ }})} + } } From 3f3ab1c3c827b81a6d6d9caadf8b275dbe16e614 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 25 Aug 2023 16:49:26 -0400 Subject: [PATCH 4/6] remove unnecessary parens --- leptos/src/transition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leptos/src/transition.rs b/leptos/src/transition.rs index c40c45859..49159543e 100644 --- a/leptos/src/transition.rs +++ b/leptos/src/transition.rs @@ -130,7 +130,7 @@ where if is_first_run(&first_run, &suspense_context) { let has_local_only = suspense_context.has_local_only() || cfg!(feature = "csr"); - if (!has_local_only || child_runs.get() > 0) { + if !has_local_only || child_runs.get() > 0 { first_run.set(false); } } From ad6eb58fe100647cb287a1fc885396bd697a52e1 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 25 Aug 2023 17:12:01 -0400 Subject: [PATCH 5/6] fix: fallback in CSR --- leptos/src/transition.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/leptos/src/transition.rs b/leptos/src/transition.rs index 49159543e..ade2d331d 100644 --- a/leptos/src/transition.rs +++ b/leptos/src/transition.rs @@ -1,8 +1,8 @@ use leptos_dom::{Fragment, HydrationCtx, IntoView, View}; use leptos_macro::component; use leptos_reactive::{ - create_isomorphic_effect, use_context, SignalGet, SignalSetter, - SuspenseContext, + create_isomorphic_effect, create_rw_signal, create_signal, use_context, + RwSignal, SignalGet, SignalSet, SignalSetter, SuspenseContext, }; use std::{ cell::{Cell, RefCell}, @@ -83,7 +83,7 @@ where { let prev_children = Rc::new(RefCell::new(None::)); - let first_run = Rc::new(std::cell::Cell::new(true)); + let first_run = create_rw_signal(true); let child_runs = Cell::new(0); let held_suspense_context = Rc::new(RefCell::new(None::)); @@ -91,17 +91,18 @@ where crate::SuspenseProps::builder() .fallback({ let prev_child = Rc::clone(&prev_children); - let first_run = Rc::clone(&first_run); move || { let suspense_context = use_context::() .expect("there to be a SuspenseContext"); + let was_first_run = + cfg!(feature = "csr") && first_run.get(); let is_first_run = - is_first_run(&first_run, &suspense_context); + is_first_run(first_run, &suspense_context); first_run.set(false); if let Some(prev_children) = &*prev_child.borrow() { - if is_first_run { + if is_first_run || was_first_run { fallback().into_view() } else { prev_children.clone() @@ -127,10 +128,12 @@ where { *prev_children.borrow_mut() = Some(frag.clone()); } - if is_first_run(&first_run, &suspense_context) { + if is_first_run(first_run, &suspense_context) { let has_local_only = suspense_context.has_local_only() || cfg!(feature = "csr"); - if !has_local_only || child_runs.get() > 0 { + if (!has_local_only || child_runs.get() > 0) + && !cfg!(feature = "csr") + { first_run.set(false); } } @@ -149,7 +152,7 @@ where } fn is_first_run( - first_run: &Rc>, + first_run: RwSignal, suspense_context: &SuspenseContext, ) -> bool { if cfg!(feature = "csr") { From 8f067dcde7bf1d4f7fe639e6ee248cfbdb28b795 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 25 Aug 2023 17:16:00 -0400 Subject: [PATCH 6/6] chore: clear release-mode warnings --- integrations/actix/src/lib.rs | 1 + leptos/src/transition.rs | 4 ++-- leptos_macro/src/view/component_builder.rs | 4 +++- leptos_reactive/src/resource.rs | 4 +++- leptos_reactive/src/runtime.rs | 9 +++++---- router/src/hooks.rs | 1 + 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs index 9b65d298f..6a4cef273 100644 --- a/integrations/actix/src/lib.rs +++ b/integrations/actix/src/lib.rs @@ -27,6 +27,7 @@ use leptos_router::*; use parking_lot::RwLock; use regex::Regex; use std::{fmt::Display, future::Future, sync::Arc}; +#[cfg(debug_assertions)] use tracing::instrument; /// This struct lets you define headers and override the status of the Response from an Element or a Server Function /// Typically contained inside of a ResponseOptions. Setting this is useful for cookies and custom responses. diff --git a/leptos/src/transition.rs b/leptos/src/transition.rs index ade2d331d..ca8cc55bd 100644 --- a/leptos/src/transition.rs +++ b/leptos/src/transition.rs @@ -1,8 +1,8 @@ use leptos_dom::{Fragment, HydrationCtx, IntoView, View}; use leptos_macro::component; use leptos_reactive::{ - create_isomorphic_effect, create_rw_signal, create_signal, use_context, - RwSignal, SignalGet, SignalSet, SignalSetter, SuspenseContext, + create_isomorphic_effect, create_rw_signal, use_context, RwSignal, + SignalGet, SignalSet, SignalSetter, SuspenseContext, }; use std::{ cell::{Cell, RefCell}, diff --git a/leptos_macro/src/view/component_builder.rs b/leptos_macro/src/view/component_builder.rs index 8cb38480e..fc8d876a4 100644 --- a/leptos_macro/src/view/component_builder.rs +++ b/leptos_macro/src/view/component_builder.rs @@ -1,6 +1,8 @@ +#[cfg(debug_assertions)] +use super::ident_from_tag_name; use super::{ client_builder::{fragment_to_tokens, TagType}, - event_from_attribute_node, ident_from_tag_name, + event_from_attribute_node, }; use proc_macro2::{Ident, TokenStream, TokenTree}; use quote::{format_ident, quote}; diff --git a/leptos_reactive/src/resource.rs b/leptos_reactive/src/resource.rs index 1a5b8a684..f310fc3de 100644 --- a/leptos_reactive/src/resource.rs +++ b/leptos_reactive/src/resource.rs @@ -1,10 +1,12 @@ +#[cfg(debug_assertions)] +use crate::SpecialNonReactiveZone; use crate::{ create_effect, create_isomorphic_effect, create_memo, create_signal, queue_microtask, runtime::with_runtime, serialization::Serializable, signal_prelude::format_signal_warning, spawn::spawn_local, use_context, GlobalSuspenseContext, Memo, ReadSignal, ScopeProperty, Signal, SignalDispose, SignalGet, SignalGetUntracked, SignalSet, SignalUpdate, - SignalWith, SpecialNonReactiveZone, SuspenseContext, WriteSignal, + SignalWith, SuspenseContext, WriteSignal, }; use std::{ any::Any, diff --git a/leptos_reactive/src/runtime.rs b/leptos_reactive/src/runtime.rs index 2e7bdc3f6..f57a26a8f 100644 --- a/leptos_reactive/src/runtime.rs +++ b/leptos_reactive/src/runtime.rs @@ -1,12 +1,13 @@ +#[cfg(debug_assertions)] +use crate::SpecialNonReactiveZone; use crate::{ hydration::SharedContext, node::{ Disposer, NodeId, ReactiveNode, ReactiveNodeState, ReactiveNodeType, }, AnyComputation, AnyResource, EffectState, Memo, MemoState, ReadSignal, - ResourceId, ResourceState, RwSignal, SerializableResource, - SpecialNonReactiveZone, StoredValueId, Trigger, UnserializableResource, - WriteSignal, + ResourceId, ResourceState, RwSignal, SerializableResource, StoredValueId, + Trigger, UnserializableResource, WriteSignal, }; use cfg_if::cfg_if; use core::hash::BuildHasherDefault; @@ -771,7 +772,7 @@ impl RuntimeId { pub(crate) fn untrack( self, f: impl FnOnce() -> T, - diagnostics: bool, + #[allow(unused)] diagnostics: bool, ) -> T { with_runtime(|runtime| { let untracked_result; diff --git a/router/src/hooks.rs b/router/src/hooks.rs index 5a1b6e677..aebe38c49 100644 --- a/router/src/hooks.rs +++ b/router/src/hooks.rs @@ -190,6 +190,7 @@ pub fn use_navigate() -> impl Fn(&str, NavigateOptions) { let to = to.to_string(); if cfg!(any(feature = "csr", feature = "hydrate")) { request_animation_frame(move || { + #[allow(unused_variables)] if let Err(e) = router.navigate_from_route(&to, &options) { leptos::debug_warn!("use_navigate error: {e:?}"); }