mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Merge pull request #1588 from leptos-rs/1457
Some resource and transition fixes
This commit is contained in:
commit
736f4185b5
11 changed files with 155 additions and 149 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:?}");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue