mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
feat: ErrorBoundary
This commit is contained in:
parent
1edec6c36a
commit
d7c62622ae
9 changed files with 556 additions and 22 deletions
|
@ -4,3 +4,4 @@ edition = "2021"
|
|||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = "0.2"
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
use std::{error, fmt, ops, sync::Arc};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
error, fmt,
|
||||
future::Future,
|
||||
ops,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
/* Wrapper Types */
|
||||
|
||||
/// This is a result type into which any error can be converted.
|
||||
///
|
||||
|
@ -39,3 +49,81 @@ where
|
|||
Error(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements behavior that allows for global or scoped error handling.
|
||||
///
|
||||
/// This allows for both "throwing" errors to register them, and "clearing" errors when they are no
|
||||
/// longer valid. This is useful for something like a user interface, in which an error can be
|
||||
/// "thrown" on some invalid user input, and later "cleared" if the user corrects the input.
|
||||
/// Keeping a unique identifier for each error allows the UI to be updated accordingly.
|
||||
pub trait ErrorHook: Send + Sync {
|
||||
/// Handles the given error, returning a unique identifier.
|
||||
fn throw(&self, error: Error) -> ErrorId;
|
||||
|
||||
/// Clears the error associated with the given identifier.
|
||||
fn clear(&self, id: &ErrorId);
|
||||
}
|
||||
|
||||
/// A unique identifier for an error. This is returned when you call [`throw`], which calls a
|
||||
/// global error handler.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Default)]
|
||||
pub struct ErrorId(usize);
|
||||
|
||||
thread_local! {
|
||||
static ERROR_HOOK: RefCell<Option<Arc<dyn ErrorHook>>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
|
||||
/// Invokes the error hook set by [`set_error_hook`] with the given error.
|
||||
pub fn throw(error: impl Into<Error>) -> ErrorId {
|
||||
ERROR_HOOK
|
||||
.with_borrow(|hook| hook.as_ref().map(|hook| hook.throw(error.into())))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Clears the given error from the current error hook.
|
||||
pub fn clear(id: &ErrorId) {
|
||||
ERROR_HOOK
|
||||
.with_borrow(|hook| hook.as_ref().map(|hook| hook.clear(id)))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pin_project_lite::pin_project! {
|
||||
/// A [`Future`] that reads the error hook that is set when it is created, and sets this as the
|
||||
/// current error hook whenever it is polled.
|
||||
pub struct ErrorHookFuture<Fut> {
|
||||
hook: Option<Arc<dyn ErrorHook>>,
|
||||
#[pin]
|
||||
inner: Fut
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut> ErrorHookFuture<Fut> {
|
||||
/// Reads the current hook and wraps the given [`Future`], returning a new `Future` that will
|
||||
/// set the error hook whenever it is polled.
|
||||
pub fn new(inner: Fut) -> Self {
|
||||
Self {
|
||||
hook: ERROR_HOOK.with_borrow(Clone::clone),
|
||||
inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut> Future for ErrorHookFuture<Fut>
|
||||
where
|
||||
Fut: Future,
|
||||
{
|
||||
type Output = Fut::Output;
|
||||
|
||||
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))
|
||||
}
|
||||
this.inner.poll(cx)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ use leptos::{
|
|||
prelude::*,
|
||||
reactive_graph::{
|
||||
computed::AsyncDerived,
|
||||
signal::{signal, RwSignal},
|
||||
signal::{signal, ArcRwSignal},
|
||||
},
|
||||
view, ErrorBoundary, IntoView, Transition,
|
||||
view, ErrorBoundary, Errors, IntoView, Transition,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
@ -54,6 +54,25 @@ pub fn fetch_example() -> impl IntoView {
|
|||
// 2) we'd need to make sure there was a thread-local spawner set up
|
||||
let cats = AsyncDerived::new_unsync(move || fetch_cats(cat_count.get()));
|
||||
|
||||
let fallback = move |errors: &ArcRwSignal<Errors>| {
|
||||
let errors = errors.clone();
|
||||
let error_list = move || {
|
||||
errors.with(|errors| {
|
||||
errors
|
||||
.iter()
|
||||
.map(|(_, e)| view! { <li>{e.to_string()}</li> })
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
};
|
||||
|
||||
view! {
|
||||
<div class="error">
|
||||
<h2>"Error"</h2>
|
||||
<ul>{error_list}</ul>
|
||||
</div>
|
||||
}
|
||||
};
|
||||
|
||||
// TODO weaving together Transition and ErrorBoundary is hard with the new async API for
|
||||
// suspense, because Transition expects a Future as its children, and ErrorBoundary isn't a
|
||||
// future
|
||||
|
@ -79,7 +98,7 @@ pub fn fetch_example() -> impl IntoView {
|
|||
}
|
||||
/>
|
||||
</label>
|
||||
<ErrorBoundary fallback=|e| view! { <p class="error">{e.to_string()}</p> }>
|
||||
<ErrorBoundary fallback>
|
||||
<Transition fallback=|| view! { <div>"Loading..."</div> }>
|
||||
{cats_view()}
|
||||
</Transition>
|
||||
|
|
|
@ -25,6 +25,7 @@ oco = { workspace = true }
|
|||
paste = "1"
|
||||
rand = { version = "0.8", optional = true }
|
||||
reactive_graph = { workspace = true, features = ["serde"] }
|
||||
rustc-hash = "1"
|
||||
tachys = { workspace = true, features = ["reactive_graph"] }
|
||||
thiserror = "1"
|
||||
tracing = "0.1"
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use crate::into_view::{IntoView, View};
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
sync::Arc,
|
||||
};
|
||||
use tachys::{
|
||||
renderer::dom::Dom,
|
||||
view::{
|
||||
|
@ -197,6 +200,12 @@ where
|
|||
/// allow the compiler to optimize the view more effectively.
|
||||
pub struct TypedChildrenMut<T>(Box<dyn FnMut() -> View<T> + Send>);
|
||||
|
||||
impl<T> Debug for TypedChildrenMut<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("TypedChildrenMut").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypedChildrenMut<T> {
|
||||
pub fn into_inner(self) -> impl FnMut() -> View<T> + Send {
|
||||
self.0
|
||||
|
|
|
@ -1,7 +1,20 @@
|
|||
use crate::{children::TypedChildrenMut, IntoView};
|
||||
use any_error::Error;
|
||||
use any_error::{Error, ErrorHook, ErrorId};
|
||||
use leptos_macro::component;
|
||||
use tachys::view::error_boundary::TryCatchBoundary;
|
||||
use reactive_graph::{
|
||||
computed::ArcMemo,
|
||||
effect::RenderEffect,
|
||||
signal::ArcRwSignal,
|
||||
traits::{Get, 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},
|
||||
};
|
||||
|
||||
///
|
||||
/// ## Beginner's Tip: ErrorBoundary Requires Your Error To Implement std::error::Error.
|
||||
|
@ -33,11 +46,319 @@ pub fn ErrorBoundary<FalFn, Fal, Chil>(
|
|||
fallback: FalFn,
|
||||
) -> impl IntoView
|
||||
where
|
||||
FalFn: FnMut(Error) -> Fal + Clone + Send + 'static,
|
||||
FalFn: FnMut(&ArcRwSignal<Errors>) -> Fal + Clone + Send + 'static,
|
||||
Fal: IntoView + 'static,
|
||||
Chil: IntoView + 'static,
|
||||
{
|
||||
let mut children = children.into_inner();
|
||||
// TODO dev-mode warning about Suspense/ErrorBoundary ordering
|
||||
move || children().catch(fallback.clone())
|
||||
let hook = Arc::new(ErrorBoundaryErrorHook::default());
|
||||
let errors = hook.errors.clone();
|
||||
let errors_empty = ArcMemo::new({
|
||||
let errors = errors.clone();
|
||||
move |_| errors.with(|map| map.is_empty())
|
||||
});
|
||||
let hook = hook as Arc<dyn ErrorHook>;
|
||||
|
||||
// provide the error hook and render children
|
||||
any_error::set_error_hook(Arc::clone(&hook));
|
||||
|
||||
ErrorBoundaryView {
|
||||
errors,
|
||||
errors_empty,
|
||||
children,
|
||||
fallback,
|
||||
fal_ty: PhantomData,
|
||||
rndr: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ErrorBoundaryView<Chil, FalFn, Fal, Rndr> {
|
||||
errors: ArcRwSignal<Errors>,
|
||||
errors_empty: ArcMemo<bool>,
|
||||
children: TypedChildrenMut<Chil>,
|
||||
fallback: FalFn,
|
||||
fal_ty: PhantomData<Fal>,
|
||||
rndr: PhantomData<Rndr>,
|
||||
}
|
||||
|
||||
impl<Chil, FalFn, Fal, Rndr> Render<Rndr>
|
||||
for ErrorBoundaryView<Chil, FalFn, Fal, Rndr>
|
||||
where
|
||||
Chil: Render<Rndr> + 'static,
|
||||
Chil::State: 'static,
|
||||
Fal: Render<Rndr> + 'static,
|
||||
Fal::State: 'static,
|
||||
FalFn: FnMut(&ArcRwSignal<Errors>) -> Fal + Send + 'static,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
type State = ErrorBoundaryViewState<Chil, Fal, Rndr>;
|
||||
type FallibleState = ();
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let Self {
|
||||
errors,
|
||||
errors_empty,
|
||||
children,
|
||||
mut fallback,
|
||||
fal_ty,
|
||||
rndr,
|
||||
} = self;
|
||||
let placeholder = Rndr::create_placeholder();
|
||||
let mut children = Some(children);
|
||||
let effect = RenderEffect::new({
|
||||
let placeholder = placeholder.clone();
|
||||
move |prev: Option<
|
||||
Either<Chil::State, (Fal::State, Chil::State)>,
|
||||
>| {
|
||||
errors_empty.track();
|
||||
if let Some(prev) = prev {
|
||||
match (errors_empty.get_untracked(), prev) {
|
||||
// no errors, and already showing children
|
||||
(true, Either::Left(children)) => {
|
||||
Either::Left(children)
|
||||
}
|
||||
// no errors, and was showing fallback
|
||||
(true, Either::Right((mut fallback, mut children))) => {
|
||||
fallback.unmount();
|
||||
Rndr::mount_before(
|
||||
&mut children,
|
||||
placeholder.as_ref(),
|
||||
);
|
||||
Either::Left(children)
|
||||
}
|
||||
// yes errors, and was showing children
|
||||
(false, Either::Left(mut chil)) => {
|
||||
chil.unmount();
|
||||
let mut fal = fallback(&errors).build();
|
||||
Rndr::mount_before(&mut fal, placeholder.as_ref());
|
||||
Either::Right((fal, chil))
|
||||
}
|
||||
// yes errors, and was showing fallback
|
||||
(false, Either::Right(_)) => todo!(),
|
||||
}
|
||||
} else {
|
||||
let children = children.take().unwrap();
|
||||
let mut children = children.into_inner();
|
||||
let children = children().into_inner().build();
|
||||
if errors_empty.get_untracked() {
|
||||
Either::Left(children)
|
||||
} else {
|
||||
Either::Right((fallback(&errors).build(), children))
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ErrorBoundaryViewState {
|
||||
effect,
|
||||
placeholder,
|
||||
chil_ty: PhantomData,
|
||||
fal_ty,
|
||||
rndr,
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {}
|
||||
|
||||
fn try_build(self) -> any_error::Result<Self::FallibleState> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn try_rebuild(
|
||||
self,
|
||||
state: &mut Self::FallibleState,
|
||||
) -> any_error::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Chil, FalFn, Fal, Rndr> RenderHtml<Rndr>
|
||||
for ErrorBoundaryView<Chil, FalFn, Fal, Rndr>
|
||||
where
|
||||
Chil: Render<Rndr> + 'static,
|
||||
Chil::State: 'static,
|
||||
Fal: Render<Rndr> + 'static,
|
||||
Fal::State: 'static,
|
||||
FalFn: FnMut(&ArcRwSignal<Errors>) -> Fal + Send + 'static,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
const MIN_LENGTH: usize = 0; //Chil::MIN_LENGTH;
|
||||
|
||||
fn to_html_with_buf(
|
||||
self,
|
||||
buf: &mut String,
|
||||
position: &mut tachys::view::Position,
|
||||
) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &tachys::hydration::Cursor<Rndr>,
|
||||
position: &tachys::view::PositionState,
|
||||
) -> Self::State {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
struct ErrorBoundaryViewState<Chil, Fal, Rndr>
|
||||
where
|
||||
Chil: Render<Rndr>,
|
||||
Chil::State: 'static,
|
||||
Fal: Render<Rndr>,
|
||||
Fal::State: 'static,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
effect: RenderEffect<Either<Chil::State, (Fal::State, Chil::State)>>,
|
||||
placeholder: Rndr::Placeholder,
|
||||
chil_ty: PhantomData<Chil>,
|
||||
fal_ty: PhantomData<Fal>,
|
||||
rndr: PhantomData<Rndr>,
|
||||
}
|
||||
|
||||
impl<Chil, Fal, Rndr> Mountable<Rndr>
|
||||
for ErrorBoundaryViewState<Chil, Fal, Rndr>
|
||||
where
|
||||
Chil: Render<Rndr>,
|
||||
Fal: Render<Rndr>,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
self.effect.with_value_mut(|state| match state {
|
||||
Either::Left(chil) => chil.unmount(),
|
||||
Either::Right((fal, _)) => fal.unmount(),
|
||||
});
|
||||
self.placeholder.unmount();
|
||||
}
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &<Rndr as Renderer>::Element,
|
||||
marker: Option<&<Rndr as Renderer>::Node>,
|
||||
) {
|
||||
self.placeholder.mount(parent, marker);
|
||||
self.effect.with_value_mut(|state| match state {
|
||||
Either::Left(chil) => {
|
||||
chil.mount(parent, Some(self.placeholder.as_ref()))
|
||||
}
|
||||
Either::Right((fal, _)) => {
|
||||
fal.mount(parent, Some(self.placeholder.as_ref()))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn insert_before_this(
|
||||
&self,
|
||||
parent: &<Rndr as Renderer>::Element,
|
||||
child: &mut dyn Mountable<Rndr>,
|
||||
) -> bool {
|
||||
self.effect
|
||||
.with_value_mut(|state| match state {
|
||||
Either::Left(chil) => chil.insert_before_this(parent, child),
|
||||
Either::Right((fal, _)) => {
|
||||
fal.insert_before_this(parent, child)
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ErrorBoundaryErrorHook {
|
||||
errors: ArcRwSignal<Errors>,
|
||||
}
|
||||
|
||||
impl ErrorHook for ErrorBoundaryErrorHook {
|
||||
fn throw(&self, error: Error) -> ErrorId {
|
||||
let key = ErrorId::default();
|
||||
self.errors.update(|map| {
|
||||
map.insert(key.clone(), error);
|
||||
});
|
||||
key
|
||||
}
|
||||
|
||||
fn clear(&self, id: &any_error::ErrorId) {
|
||||
self.errors.update(|map| {
|
||||
map.remove(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct to hold all the possible errors that could be provided by child Views
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct Errors(FxHashMap<ErrorId, Error>);
|
||||
|
||||
impl Errors {
|
||||
/// Returns `true` if there are no errors.
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Add an error to Errors that will be processed by `<ErrorBoundary/>`
|
||||
pub fn insert<E>(&mut self, key: ErrorId, error: E)
|
||||
where
|
||||
E: Into<Error>,
|
||||
{
|
||||
self.0.insert(key, error.into());
|
||||
}
|
||||
|
||||
/// Add an error with the default key for errors outside the reactive system
|
||||
pub fn insert_with_default_key<E>(&mut self, error: E)
|
||||
where
|
||||
E: Into<Error>,
|
||||
{
|
||||
self.0.insert(Default::default(), error.into());
|
||||
}
|
||||
|
||||
/// Remove an error to Errors that will be processed by `<ErrorBoundary/>`
|
||||
pub fn remove(&mut self, key: &ErrorId) -> Option<Error> {
|
||||
self.0.remove(key)
|
||||
}
|
||||
|
||||
/// An iterator over all the errors, in arbitrary order.
|
||||
#[inline(always)]
|
||||
pub fn iter(&self) -> Iter<'_> {
|
||||
Iter(self.0.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Errors {
|
||||
type Item = (ErrorId, Error);
|
||||
type IntoIter = IntoIter;
|
||||
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IntoIter(self.0.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
/// An owning iterator over all the errors contained in the [`Errors`] struct.
|
||||
#[repr(transparent)]
|
||||
pub struct IntoIter(std::collections::hash_map::IntoIter<ErrorId, Error>);
|
||||
|
||||
impl Iterator for IntoIter {
|
||||
type Item = (ErrorId, Error);
|
||||
|
||||
#[inline(always)]
|
||||
fn next(
|
||||
&mut self,
|
||||
) -> std::option::Option<<Self as std::iter::Iterator>::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over all the errors contained in the [`Errors`] struct.
|
||||
#[repr(transparent)]
|
||||
pub struct Iter<'a>(std::collections::hash_map::Iter<'a, ErrorId, Error>);
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = (&'a ErrorId, &'a Error);
|
||||
|
||||
#[inline(always)]
|
||||
fn next(
|
||||
&mut self,
|
||||
) -> std::option::Option<<Self as std::iter::Iterator>::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,12 @@ pub struct View<T>(T)
|
|||
where
|
||||
T: Sized;
|
||||
|
||||
impl<T> View<T> {
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoView: Sized + Render<Dom> + RenderHtml<Dom> + Send
|
||||
//+ AddAnyAttr<Dom>
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::{Position, PositionState, RenderHtml};
|
||||
use crate::{
|
||||
hydration::Cursor,
|
||||
renderer::CastFrom,
|
||||
ssr::StreamBuilder,
|
||||
view::{Mountable, Render, Renderer},
|
||||
};
|
||||
|
@ -13,30 +14,103 @@ where
|
|||
R: Renderer,
|
||||
E: Into<AnyError> + 'static,
|
||||
{
|
||||
type State = <Option<T> as Render<R>>::State;
|
||||
type State = ResultState<T::State, R>;
|
||||
type FallibleState = T::State;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
self.ok().build()
|
||||
let placeholder = R::create_placeholder();
|
||||
let state = match self {
|
||||
Ok(view) => Ok(view.build()),
|
||||
Err(e) => Err(any_error::throw(e.into())),
|
||||
};
|
||||
ResultState { placeholder, state }
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.ok().rebuild(state);
|
||||
match (&mut state.state, self) {
|
||||
// both errors: throw the new error and replace
|
||||
(Err(prev), Err(new)) => {
|
||||
*prev = any_error::throw(new.into());
|
||||
}
|
||||
// both Ok: need to rebuild child
|
||||
(Ok(old), Ok(new)) => {
|
||||
T::rebuild(new, old);
|
||||
}
|
||||
// Ok => Err: unmount, replace with marker, and throw
|
||||
(Ok(old), Err(err)) => {
|
||||
old.unmount();
|
||||
state.state = Err(any_error::throw(err));
|
||||
}
|
||||
// Err => Ok: clear error and build
|
||||
(Err(err), Ok(new)) => {
|
||||
any_error::clear(err);
|
||||
let mut new_state = new.build();
|
||||
R::mount_before(&mut new_state, state.placeholder.as_ref());
|
||||
state.state = Ok(new_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_build(self) -> any_error::Result<Self::FallibleState> {
|
||||
let inner = self.map_err(Into::into)?;
|
||||
let state = inner.build();
|
||||
Ok(state)
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn try_rebuild(
|
||||
self,
|
||||
state: &mut Self::FallibleState,
|
||||
) -> any_error::Result<()> {
|
||||
let inner = self.map_err(Into::into)?;
|
||||
inner.rebuild(state);
|
||||
Ok(())
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// View state for a `Result<_, _>` view.
|
||||
pub struct ResultState<T, R>
|
||||
where
|
||||
T: Mountable<R>,
|
||||
R: Renderer,
|
||||
{
|
||||
/// Marks the location of this view.
|
||||
placeholder: R::Placeholder,
|
||||
/// The view state.
|
||||
state: Result<T, any_error::ErrorId>,
|
||||
}
|
||||
|
||||
impl<T, R> Mountable<R> for ResultState<T, R>
|
||||
where
|
||||
T: Mountable<R>,
|
||||
R: Renderer,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
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();
|
||||
}
|
||||
|
||||
fn mount(&mut self, parent: &R::Element, marker: Option<&R::Node>) {
|
||||
self.placeholder.mount(parent, marker);
|
||||
if let Ok(ref mut state) = self.state {
|
||||
state.mount(parent, Some(self.placeholder.as_ref()));
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_before_this(
|
||||
&self,
|
||||
parent: &R::Element,
|
||||
child: &mut dyn Mountable<R>,
|
||||
) -> bool {
|
||||
if self
|
||||
.state
|
||||
.as_ref()
|
||||
.map(|n| n.insert_before_this(parent, child))
|
||||
== Ok(true)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
self.placeholder.insert_before_this(parent, child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +156,22 @@ where
|
|||
cursor: &Cursor<R>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
self.ok().hydrate::<FROM_SERVER>(cursor, position)
|
||||
// hydrate the state, if it exists
|
||||
let state = self
|
||||
.map(|s| s.hydrate::<FROM_SERVER>(cursor, position))
|
||||
.map_err(|e| any_error::throw(e.into()));
|
||||
|
||||
// pull the placeholder
|
||||
if position.get() == Position::FirstChild {
|
||||
cursor.child();
|
||||
} else {
|
||||
cursor.sibling();
|
||||
}
|
||||
let placeholder = cursor.current().to_owned();
|
||||
let placeholder = R::Placeholder::cast_from(placeholder).unwrap();
|
||||
position.set(Position::NextChild);
|
||||
|
||||
ResultState { placeholder, state }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ where
|
|||
if let Some(ref mut state) = self.state {
|
||||
state.unmount();
|
||||
}
|
||||
R::remove(self.placeholder.as_ref());
|
||||
self.placeholder.unmount();
|
||||
}
|
||||
|
||||
fn mount(&mut self, parent: &R::Element, marker: Option<&R::Node>) {
|
||||
|
|
Loading…
Reference in a new issue