fix ErrorBoundary/Suspense

This commit is contained in:
Greg Johnston 2024-04-04 20:23:00 -04:00
parent c06110128b
commit cc2714c03d
8 changed files with 62 additions and 33 deletions

View file

@ -25,7 +25,7 @@ type CatCount = usize;
async fn fetch_cats(count: CatCount) -> Result<Vec<String>> {
if count > 0 {
gloo_timers::future::TimeoutFuture::new(1000).await;
gloo_timers::future::TimeoutFuture::new(250).await;
// make the request
let res = reqwasm::http::Request::get(&format!(
"https://api.thecatapi.com/v1/images/search?limit={count}",
@ -103,7 +103,7 @@ pub fn fetch_example() -> impl IntoView {
/>
</label>
<ErrorBoundary fallback>
<Suspense fallback=|| view! { <div>"Loading..."</div> }>
<Transition fallback=|| view! { <div>"Loading..."</div> }>
<ul>
{
async move {
@ -116,7 +116,7 @@ pub fn fetch_example() -> impl IntoView {
.suspend()
}
</ul>
</Suspense>
</Transition>
</ErrorBoundary>
</div>
}

View file

@ -5,13 +5,12 @@ use reactive_graph::{
computed::ArcMemo,
effect::RenderEffect,
signal::ArcRwSignal,
traits::{Get, GetUntracked, Track, Update, With},
traits::{GetUntracked, Track, Update, With},
};
use rustc_hash::FxHashMap;
use std::{marker::PhantomData, sync::Arc};
use tachys::{
either::Either,
reactive_graph::RenderEffectState,
renderer::Renderer,
view::{Mountable, Render, RenderHtml},
};
@ -232,7 +231,7 @@ where
Either::Left(chil) => chil.unmount(),
Either::Right((fal, _)) => fal.unmount(),
});
//self.placeholder.unmount();
self.placeholder.unmount();
}
fn mount(

View file

@ -47,13 +47,10 @@ where
let fallback = move || fallback.clone().run();
// TODO check this against islands
move || {
crate::logging::log!("running innner thing");
untrack(|| {
SuspenseBoundary::<false, _, _>::new(
fallback.clone(),
(children.clone())(),
)
})
SuspenseBoundary::<false, _, _>::new(
fallback.clone(),
(children.clone())(),
)
// TODO track
}
}

View file

@ -1,25 +1,27 @@
use crate::{children::ViewFn, AsyncChildren, IntoView};
use crate::{
children::{TypedChildrenFn, ViewFn},
IntoView,
};
use leptos_macro::component;
use std::{future::Future, sync::Arc};
use tachys::prelude::FutureViewExt;
use tachys::async_views::SuspenseBoundary;
/// TODO docs!
#[component]
pub fn Transition<Chil, ChilFn, ChilFut>(
pub fn Transition<Chil>(
#[prop(optional, into)] fallback: ViewFn,
children: AsyncChildren<Chil, ChilFn, ChilFut>,
children: TypedChildrenFn<Chil>,
) -> impl IntoView
where
Chil: IntoView + 'static,
ChilFn: Fn() -> ChilFut + Clone + Send + 'static,
ChilFut: Future<Output = Chil> + Send + 'static,
{
let children = children.into_inner();
let fallback = move || fallback.clone().run();
// TODO check this against islands
move || {
children()
.suspend()
.transition()
.with_fallback(fallback.run())
.track()
SuspenseBoundary::<true, _, _>::new(
fallback.clone(),
(children.clone())(),
)
// TODO track
}
}

View file

@ -96,7 +96,8 @@ where
async move {
let value = fut.await;
let mut state = state.borrow_mut();
let fut = Either::<Fal, Chil::AsyncOutput>::Right(value)
Either::<Fal, Chil::AsyncOutput>::Right(value)
.rebuild(&mut *state);
}
});

View file

@ -77,12 +77,30 @@ pub trait Renderer: Sized + Debug {
M: Mountable<Self>,
{
let parent = Self::Element::cast_from(
Self::get_parent(before).expect("node should have parent"),
Self::get_parent(before).expect("could not find parent element"),
)
.expect("placeholder parent should be Element");
new_child.mount(&parent, Some(before));
}
/// Tries to mount the new child before the marker as its sibling.
///
/// Returns `false` if the child did not have a valid parent.
#[track_caller]
fn try_mount_before<M>(new_child: &mut M, before: &Self::Node) -> bool
where
M: Mountable<Self>,
{
if let Some(parent) =
Self::get_parent(before).and_then(Self::Element::cast_from)
{
new_child.mount(&parent, Some(before));
true
} else {
false
}
}
/// Removes the child node from the parents, and returns the removed node.
fn remove_node(
parent: &Self::Element,

View file

@ -52,13 +52,13 @@ where
(Either::Right(new), Either::Left(old)) => {
old.unmount();
let mut new_state = new.build();
Rndr::mount_before(&mut new_state, marker);
Rndr::try_mount_before(&mut new_state, marker);
state.state = Either::Right(new_state);
}
(Either::Left(new), Either::Right(old)) => {
old.unmount();
let mut new_state = new.build();
Rndr::mount_before(&mut new_state, marker);
Rndr::try_mount_before(&mut new_state, marker);
state.state = Either::Left(new_state);
}
}

View file

@ -46,7 +46,7 @@ where
(Err(err), Ok(new)) => {
any_error::clear(err);
let mut new_state = new.build();
R::mount_before(&mut new_state, state.placeholder.as_ref());
R::try_mount_before(&mut new_state, state.placeholder.as_ref());
state.state = Ok(new_state);
}
}
@ -83,6 +83,20 @@ where
state: Result<T, any_error::ErrorId>,
}
impl<T, R> Drop for ResultState<T, R>
where
T: Mountable<R>,
R: Renderer,
{
fn drop(&mut self) {
// when the state is cleared, unregister this error; this item is being dropped and its
// error should no longer be shown
if let Err(e) = &self.state {
any_error::clear(e);
}
}
}
impl<T, R> Mountable<R> for ResultState<T, R>
where
T: Mountable<R>,
@ -92,9 +106,7 @@ where
if let Ok(ref mut state) = self.state {
state.unmount();
}
// TODO investigate: including this seems to break error boundaries, although it doesn't
// make sense to me why it would be a problem
// self.placeholder.unmount();
self.placeholder.unmount();
}
fn mount(&mut self, parent: &R::Element, marker: Option<&R::Node>) {