mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
fix: unique IDs and correct hydration for <ErrorBoundary/>
(closes #2704)
This commit is contained in:
parent
25bfc27544
commit
d9b590b8e0
5 changed files with 58 additions and 16 deletions
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "throw_error"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0-beta"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::{
|
|||
error,
|
||||
fmt::{self, Display},
|
||||
future::Future,
|
||||
ops,
|
||||
mem, ops,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
|
@ -92,9 +92,25 @@ thread_local! {
|
|||
static ERROR_HOOK: RefCell<Option<Arc<dyn ErrorHook>>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
/// Resets the error hook to its previous state when dropped.
|
||||
pub struct ResetErrorHookOnDrop(Option<Arc<dyn ErrorHook>>);
|
||||
|
||||
impl Drop for ResetErrorHookOnDrop {
|
||||
fn drop(&mut self) {
|
||||
ERROR_HOOK.with_borrow_mut(|this| *this = self.0.take())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current error hook.
|
||||
pub fn get_error_hook() -> Option<Arc<dyn ErrorHook>> {
|
||||
ERROR_HOOK.with_borrow(Clone::clone)
|
||||
}
|
||||
|
||||
/// Sets the current thread-local error hook, which will be invoked when [`throw`] is called.
|
||||
pub fn set_error_hook(hook: Arc<dyn ErrorHook>) {
|
||||
ERROR_HOOK.with_borrow_mut(|this| *this = Some(hook))
|
||||
pub fn set_error_hook(hook: Arc<dyn ErrorHook>) -> ResetErrorHookOnDrop {
|
||||
ResetErrorHookOnDrop(
|
||||
ERROR_HOOK.with_borrow_mut(|this| mem::replace(this, Some(hook))),
|
||||
)
|
||||
}
|
||||
|
||||
/// Invokes the error hook set by [`set_error_hook`] with the given error.
|
||||
|
@ -140,9 +156,10 @@ where
|
|||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
if let Some(hook) = &this.hook {
|
||||
set_error_hook(Arc::clone(hook))
|
||||
}
|
||||
let _hook = this
|
||||
.hook
|
||||
.as_ref()
|
||||
.map(|hook| set_error_hook(Arc::clone(hook)));
|
||||
this.inner.poll(cx)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,12 @@ pub type PinnedStream<T> = Pin<Box<dyn Stream<Item = T> + Send + Sync>>;
|
|||
/// from the server to the client.
|
||||
pub struct SerializedDataId(usize);
|
||||
|
||||
impl From<SerializedDataId> for ErrorId {
|
||||
fn from(value: SerializedDataId) -> Self {
|
||||
value.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Information that will be shared between the server and the client.
|
||||
pub trait SharedContext: Debug {
|
||||
/// Whether the application is running in the browser.
|
||||
|
|
|
@ -9,7 +9,7 @@ use reactive_graph::{
|
|||
traits::{Get, Update, With, WithUntracked},
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||
use tachys::{
|
||||
html::attribute::Attribute,
|
||||
hydration::Cursor,
|
||||
|
@ -72,12 +72,11 @@ where
|
|||
});
|
||||
let hook = hook as Arc<dyn ErrorHook>;
|
||||
|
||||
// provide the error hook and render children
|
||||
// TODO unset this outside the ErrorBoundary
|
||||
throw_error::set_error_hook(Arc::clone(&hook));
|
||||
let _guard = throw_error::set_error_hook(Arc::clone(&hook));
|
||||
let children = children.into_inner()();
|
||||
|
||||
ErrorBoundaryView {
|
||||
hook,
|
||||
boundary_id,
|
||||
errors_empty,
|
||||
children,
|
||||
|
@ -87,8 +86,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ErrorBoundaryView<Chil, FalFn, Rndr> {
|
||||
hook: Arc<dyn ErrorHook>,
|
||||
boundary_id: SerializedDataId,
|
||||
errors_empty: ArcMemo<bool>,
|
||||
children: Chil,
|
||||
|
@ -146,10 +145,12 @@ where
|
|||
|
||||
fn build(mut self) -> Self::State {
|
||||
let mut children = Some(self.children.build());
|
||||
let hook = Arc::clone(&self.hook);
|
||||
RenderEffect::new(
|
||||
move |prev: Option<
|
||||
ErrorBoundaryViewState<Chil::State, Fal::State>,
|
||||
>| {
|
||||
let _hook = throw_error::set_error_hook(Arc::clone(&hook));
|
||||
if let Some(mut state) = prev {
|
||||
match (self.errors_empty.get(), &mut state.fallback) {
|
||||
// no errors, and was showing fallback
|
||||
|
@ -216,6 +217,7 @@ where
|
|||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
{
|
||||
let ErrorBoundaryView {
|
||||
hook,
|
||||
boundary_id,
|
||||
errors_empty,
|
||||
children,
|
||||
|
@ -224,6 +226,7 @@ where
|
|||
rndr,
|
||||
} = self;
|
||||
ErrorBoundaryView {
|
||||
hook,
|
||||
boundary_id,
|
||||
errors_empty,
|
||||
children: children.add_any_attr(attr.into_cloneable_owned()),
|
||||
|
@ -252,6 +255,7 @@ where
|
|||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
let ErrorBoundaryView {
|
||||
hook,
|
||||
boundary_id,
|
||||
errors_empty,
|
||||
children,
|
||||
|
@ -260,6 +264,7 @@ where
|
|||
..
|
||||
} = self;
|
||||
ErrorBoundaryView {
|
||||
hook,
|
||||
boundary_id,
|
||||
errors_empty,
|
||||
children: children.resolve().await,
|
||||
|
@ -277,6 +282,7 @@ where
|
|||
mark_branches: bool,
|
||||
) {
|
||||
// first, attempt to serialize the children to HTML, then check for errors
|
||||
let _hook = throw_error::set_error_hook(self.hook);
|
||||
let mut new_buf = String::with_capacity(Chil::MIN_LENGTH);
|
||||
let mut new_pos = *position;
|
||||
self.children.to_html_with_buf(
|
||||
|
@ -309,6 +315,7 @@ where
|
|||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let hook = throw_error::set_error_hook(self.hook);
|
||||
// first, attempt to serialize the children to HTML, then check for errors
|
||||
let mut new_buf = StreamBuilder::new(buf.clone_id());
|
||||
let mut new_pos = *position;
|
||||
|
@ -345,12 +352,14 @@ where
|
|||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let mut children = Some(self.children);
|
||||
let hook = Arc::clone(&self.hook);
|
||||
let cursor = cursor.to_owned();
|
||||
let position = position.to_owned();
|
||||
RenderEffect::new(
|
||||
move |prev: Option<
|
||||
ErrorBoundaryViewState<Chil::State, Fal::State>,
|
||||
>| {
|
||||
let _hook = throw_error::set_error_hook(Arc::clone(&hook));
|
||||
if let Some(mut state) = prev {
|
||||
match (self.errors_empty.get(), &mut state.fallback) {
|
||||
// no errors, and was showing fallback
|
||||
|
@ -424,7 +433,10 @@ impl ErrorBoundaryErrorHook {
|
|||
impl ErrorHook for ErrorBoundaryErrorHook {
|
||||
fn throw(&self, error: Error) -> ErrorId {
|
||||
// generate a unique ID
|
||||
let key = ErrorId::default(); // TODO unique ID...
|
||||
let key: ErrorId = Owner::current_shared_context()
|
||||
.map(|sc| sc.next_id())
|
||||
.unwrap_or_default()
|
||||
.into();
|
||||
|
||||
// register it with the shared context, so that it can be serialized from server to client
|
||||
// as needed
|
||||
|
|
|
@ -6,7 +6,8 @@ use crate::{
|
|||
view::{iterators::OptionState, Mountable, Render, Renderer},
|
||||
};
|
||||
use either_of::Either;
|
||||
use throw_error::Error as AnyError;
|
||||
use std::sync::Arc;
|
||||
use throw_error::{Error as AnyError, ErrorHook};
|
||||
|
||||
impl<R, T, E> Render<R> for Result<T, E>
|
||||
where
|
||||
|
@ -17,6 +18,7 @@ where
|
|||
type State = ResultState<T, R>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let hook = throw_error::get_error_hook();
|
||||
let (state, error) = match self {
|
||||
Ok(view) => (Either::Left(view.build()), None),
|
||||
Err(e) => (
|
||||
|
@ -24,10 +26,11 @@ where
|
|||
Some(throw_error::throw(e.into())),
|
||||
),
|
||||
};
|
||||
ResultState { state, error }
|
||||
ResultState { state, error, hook }
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
let _guard = state.hook.clone().map(throw_error::set_error_hook);
|
||||
match (&mut state.state, self) {
|
||||
// both errors: throw the new error and replace
|
||||
(Either::Right(_), Err(new)) => {
|
||||
|
@ -68,6 +71,7 @@ where
|
|||
/// The view state.
|
||||
state: OptionState<T, R>,
|
||||
error: Option<throw_error::ErrorId>,
|
||||
hook: Option<Arc<dyn ErrorHook>>,
|
||||
}
|
||||
|
||||
impl<T, R> Drop for ResultState<T, R>
|
||||
|
@ -164,6 +168,7 @@ where
|
|||
inner.to_html_with_buf(buf, position, escape, mark_branches)
|
||||
}
|
||||
Err(e) => {
|
||||
buf.push_str("<!>");
|
||||
throw_error::throw(e);
|
||||
}
|
||||
}
|
||||
|
@ -186,6 +191,7 @@ where
|
|||
mark_branches,
|
||||
),
|
||||
Err(e) => {
|
||||
buf.push_sync("<!>");
|
||||
throw_error::throw(e);
|
||||
}
|
||||
}
|
||||
|
@ -196,6 +202,7 @@ where
|
|||
cursor: &Cursor<R>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let hook = throw_error::get_error_hook();
|
||||
let (state, error) = match self {
|
||||
Ok(view) => (
|
||||
Either::Left(view.hydrate::<FROM_SERVER>(cursor, position)),
|
||||
|
@ -210,6 +217,6 @@ where
|
|||
(Either::Right(state), Some(throw_error::throw(e.into())))
|
||||
}
|
||||
};
|
||||
ResultState { state, error }
|
||||
ResultState { state, error, hook }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue