Merge pull request #1588 from leptos-rs/1457

Some resource and transition fixes
This commit is contained in:
Greg Johnston 2023-08-26 07:34:21 -04:00 committed by GitHub
commit 736f4185b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 155 additions and 149 deletions

View file

@ -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 {
}}
</span>
<span>"page " {page}</span>
<Transition
fallback=move || view! { <p>"Loading..."</p> }
<span class="page-link"
class:disabled=hide_more_link
aria-hidden=hide_more_link
>
<span class="page-link"
class:disabled=hide_more_link
aria-hidden=hide_more_link
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
aria-label="Next Page"
>
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
aria-label="Next Page"
>
"more >"
</a>
</span>
</Transition>
"more >"
</a>
</span>
</div>
<main class="news-list">
<div>

View file

@ -25,48 +25,45 @@ pub fn Story() -> impl IntoView {
};
view! {
<>
<Suspense fallback=|| view! { "Loading..." }>
<Meta name="description" content=meta_description/>
<Suspense fallback=|| view! { "Loading..." }>
{move || story.get().map(|story| match story {
None => view! { <div class="item-view">"Error loading this story."</div> },
Some(story) => view! {
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{user}")>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move |comment| view! { <Comment comment /> }
/>
</ul>
</div>
{move || story.get().map(|story| match story {
None => view! { <div class="item-view">"Error loading this story."</div> },
Some(story) => view! {
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{user}")>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
</div>
}})
}
</Suspense>
</>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move |comment| view! { <Comment comment /> }
/>
</ul>
</div>
</div>
}})}
</Suspense>
}
}

View file

@ -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! {
<div class="news-view">
<div class="news-list-nav">
<span>
@ -65,20 +64,16 @@ pub fn Stories() -> impl IntoView {
}}
</span>
<span>"page " {page}</span>
<Transition
fallback=move || view! { <p>"Loading..."</p> }
<span class="page-link"
class:disabled=hide_more_link
aria-hidden=hide_more_link
>
<span class="page-link"
class:disabled=hide_more_link
aria-hidden=hide_more_link
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
aria-label="Next Page"
>
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
aria-label="Next Page"
>
"more >"
</a>
</span>
</Transition>
"more >"
</a>
</span>
</div>
<main class="news-list">
<div>

View file

@ -25,48 +25,45 @@ pub fn Story() -> impl IntoView {
};
view! {
<>
<Suspense fallback=|| view! { "Loading..." }>
<Meta name="description" content=meta_description/>
<Suspense fallback=|| view! { "Loading..." }>
{move || story.get().map(|story| match story {
None => view! { <div class="item-view">"Error loading this story."</div> },
Some(story) => view! {
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{user}")>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move | comment| view! { <Comment comment /> }
/>
</ul>
</div>
{move || story.get().map(|story| match story {
None => view! { <div class="item-view">"Error loading this story."</div> },
Some(story) => view! {
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{user}")>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
</div>
}})
}
</Suspense>
</>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move | comment| view! { <Comment comment /> }
/>
</ul>
</div>
</div>
}})}
</Suspense>
}
}

View file

@ -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.

View file

@ -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, 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::<View>));
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::<SuspenseContext>));
@ -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::<SuspenseContext>()
.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,12 +128,11 @@ 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)
&& (cfg!(feature = "csr")
|| HydrationCtx::is_hydrating())
&& !cfg!(feature = "csr")
{
first_run.set(false);
}
@ -152,7 +152,7 @@ where
}
fn is_first_run(
first_run: &Rc<Cell<bool>>,
first_run: RwSignal<bool>,
suspense_context: &SuspenseContext,
) -> bool {
if cfg!(feature = "csr") {

View file

@ -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};

View file

@ -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<ResourceId>,
/// Resources that have not yet resolved.
pub pending_resources: HashSet<ResourceId>,
/// Resources that have already resolved.
@ -201,24 +203,27 @@ impl Default for SharedContext {
let pending_resources: HashSet<ResourceId> = 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(),

View file

@ -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,
@ -504,10 +506,22 @@ where
instrument(level = "trace", skip_all,)
)]
pub fn loading(&self) -> Signal<bool> {
let loading = with_runtime(|runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
resource.loading
})
#[allow(unused_variables)]
let (loading, is_from_server) = with_runtime(|runtime| {
let loading = runtime
.resource(self.id, |resource: &ResourceState<S, T>| {
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 +533,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::<SuspenseContext>().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::<SuspenseContext>().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::<SuspenseContext>().is_none()
{
true
} else {
loading.get()
}
})
}
#[cfg(not(feature = "hydrate"))]
{
loading.into()
@ -785,6 +795,7 @@ where
)
)]
#[inline(always)]
#[track_caller]
fn try_get(&self) -> Option<Option<T>> {
let location = std::panic::Location::caller();
with_runtime(|runtime| {

View file

@ -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<T>(
self,
f: impl FnOnce() -> T,
diagnostics: bool,
#[allow(unused)] diagnostics: bool,
) -> T {
with_runtime(|runtime| {
let untracked_result;

View file

@ -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:?}");
}