Bläddra i källkod

polish the throw trait

Evan Almloff 1 år sedan
förälder
incheckning
6141aeb037

+ 3 - 0
Cargo.toml

@@ -133,3 +133,6 @@ fern = { version = "0.6.0", features = ["colored"] }
 env_logger = "0.10.0"
 simple_logger = "4.0.0"
 thiserror = { workspace = true }
+
+[dependencies]
+tracing-subscriber = "0.3.17"

+ 11 - 16
examples/error_handle.rs

@@ -1,4 +1,4 @@
-use dioxus::prelude::*;
+use dioxus::{core::CapturedError, prelude::*};
 
 fn main() {
     dioxus_desktop::launch(App);
@@ -6,30 +6,25 @@ fn main() {
 
 #[component]
 fn App(cx: Scope) -> Element {
-    let val = use_state(cx, || "0.0001");
-
-    let num = match val.parse::<f32>() {
-        Err(_) => return cx.render(rsx!("Parsing failed")),
-        Ok(num) => num,
-    };
-
     cx.render(rsx! {
-        h1 { "The parsed value is {num}" }
-        button {
-            onclick: move |_| val.set("invalid"),
-            "Set an invalid number"
+        ErrorBoundary {
+            handle_error: |error: CapturedError| rsx! {"Found error {error}"},
+            DemoC {
+                x: 1
+            }
         }
-        (0..5).map(|i| rsx! {
-            DemoC { x: i }
-        })
     })
 }
 
 #[component]
 fn DemoC(cx: Scope, x: i32) -> Element {
+    let result = Err("Error");
+
+    result.throw()?;
+
     cx.render(rsx! {
         h1 {
-            "asdasdasdasd {x}"
+            "{x}"
         }
     })
 }

+ 1 - 1
packages/core/src/diff.rs

@@ -560,7 +560,7 @@ impl<'b> VirtualDom {
         // If none of the old keys are reused by the new children, then we remove all the remaining old children and
         // create the new children afresh.
         if shared_keys.is_empty() {
-            if old.get(0).is_some() {
+            if !old.is_empty() {
                 self.remove_nodes(&old[1..]);
                 self.replace(&old[0], new);
             } else {

+ 343 - 57
packages/core/src/error_boundary.rs

@@ -1,25 +1,68 @@
-use crate::{ScopeId, ScopeState};
+use crate::{
+    scope_context::{consume_context, current_scope_id, schedule_update_any},
+    Element, IntoDynNode, LazyNodes, Properties, Scope, ScopeId, ScopeState, Template,
+    TemplateAttribute, TemplateNode, VNode,
+};
 use std::{
     any::{Any, TypeId},
+    backtrace::Backtrace,
     cell::RefCell,
-    fmt::Debug,
+    error::Error,
+    fmt::{Debug, Display},
+    rc::Rc,
+    sync::Arc,
 };
 
+/// Provide an error boundary to catch errors from child components
+pub fn use_error_boundary(cx: &ScopeState) -> &ErrorBoundary {
+    cx.use_hook(|| cx.provide_context(ErrorBoundary::new()))
+}
+
 /// A boundary that will capture any errors from child components
+#[derive(Debug, Clone, Default)]
 pub struct ErrorBoundary {
+    inner: Rc<ErrorBoundaryInner>,
+}
+
+/// A boundary that will capture any errors from child components
+pub struct ErrorBoundaryInner {
     error: RefCell<Option<CapturedError>>,
     _id: ScopeId,
+    rerun_boundary: Arc<dyn Fn(ScopeId) + Send + Sync>,
 }
 
+impl Debug for ErrorBoundaryInner {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("ErrorBoundaryInner")
+            .field("error", &self.error)
+            .finish()
+    }
+}
+
+#[derive(Debug)]
 /// An instance of an error captured by a descendant component.
 pub struct CapturedError {
     /// The error captured by the error boundary
     pub error: Box<dyn Debug + 'static>,
 
+    /// The backtrace of the error
+    pub backtrace: Backtrace,
+
     /// The scope that threw the error
     pub scope: ScopeId,
 }
 
+impl Display for CapturedError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_fmt(format_args!(
+            "Encountered error: {:?}\nIn scope: {:?}\nBacktrace: {}",
+            self.error, self.scope, self.backtrace
+        ))
+    }
+}
+
+impl Error for CapturedError {}
+
 impl CapturedError {
     /// Downcast the error type into a concrete error type
     pub fn downcast<T: 'static>(&self) -> Option<&T> {
@@ -32,17 +75,56 @@ impl CapturedError {
     }
 }
 
-impl ErrorBoundary {
-    pub fn new(id: ScopeId) -> Self {
+impl Default for ErrorBoundaryInner {
+    fn default() -> Self {
         Self {
             error: RefCell::new(None),
-            _id: id,
+            _id: current_scope_id()
+                .expect("Cannot create an error boundary outside of a component's scope."),
+            rerun_boundary: schedule_update_any().unwrap(),
+        }
+    }
+}
+
+impl ErrorBoundary {
+    /// Create a new error boundary
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Create a new error boundary in the current scope
+    pub(crate) fn new_in_scope(
+        scope: ScopeId,
+        rerun_boundary: Arc<dyn Fn(ScopeId) + Send + Sync>,
+    ) -> Self {
+        Self {
+            inner: Rc::new(ErrorBoundaryInner {
+                error: RefCell::new(None),
+                _id: scope,
+                rerun_boundary,
+            }),
         }
     }
 
     /// Push an error into this Error Boundary
-    pub fn insert_error(&self, scope: ScopeId, error: Box<dyn Debug + 'static>) {
-        self.error.replace(Some(CapturedError { error, scope }));
+    pub fn insert_error(
+        &self,
+        scope: ScopeId,
+        error: Box<dyn Debug + 'static>,
+        backtrace: Backtrace,
+    ) {
+        println!("{:?} {:?}", error, self.inner._id);
+        self.inner.error.replace(Some(CapturedError {
+            error,
+            scope,
+            backtrace,
+        }));
+        (self.inner.rerun_boundary)(self.inner._id);
+    }
+
+    /// Take any error that has been captured by this error boundary
+    pub fn take_error(&self) -> Option<CapturedError> {
+        self.inner.error.take()
     }
 }
 
@@ -59,7 +141,7 @@ impl ErrorBoundary {
 /// ```rust, ignore
 /// #[component]
 /// fn App(cx: Scope, count: String) -> Element {
-///     let id: i32 = count.parse().throw(cx)?;
+///     let id: i32 = count.parse().throw()?;
 ///
 ///     cx.render(rsx! {
 ///         div { "Count {}" }
@@ -84,14 +166,14 @@ pub trait Throw<S = ()>: Sized {
     /// ```rust, ignore
     /// #[component]
     /// fn App(cx: Scope, count: String) -> Element {
-    ///     let id: i32 = count.parse().throw(cx)?;
+    ///     let id: i32 = count.parse().throw()?;
     ///
     ///     cx.render(rsx! {
     ///         div { "Count {}" }
     ///     })
     /// }
     /// ```
-    fn throw(self, cx: &ScopeState) -> Option<Self::Out>;
+    fn throw(self) -> Option<Self::Out>;
 
     /// Returns an option that evaluates to None if there is an error, injecting the error to the nearest error boundary.
     ///
@@ -107,45 +189,46 @@ pub trait Throw<S = ()>: Sized {
     /// ```rust, ignore
     /// #[component]
     /// fn App(cx: Scope, count: String) -> Element {
-    ///     let id: i32 = count.parse().throw(cx)?;
+    ///     let id: i32 = count.parse().throw()?;
     ///
     ///     cx.render(rsx! {
     ///         div { "Count {}" }
     ///     })
     /// }
     /// ```
-    fn throw_with<D: Debug + 'static>(
-        self,
-        cx: &ScopeState,
-        e: impl FnOnce() -> D,
-    ) -> Option<Self::Out>;
+    fn throw_with<D: Debug + 'static>(self, e: impl FnOnce() -> D) -> Option<Self::Out> {
+        self.throw().or_else(|| throw_error(e()))
+    }
+}
+
+fn throw_error<T>(e: impl Debug + 'static) -> Option<T> {
+    if let Some(cx) = consume_context::<ErrorBoundary>() {
+        match current_scope_id() {
+            Some(id) => cx.insert_error(id, Box::new(e), Backtrace::capture()),
+            None => {
+                tracing::error!("Cannot throw error outside of a component's scope.")
+            }
+        }
+    }
+
+    None
 }
 
 /// We call clone on any errors that can be owned out of a reference
 impl<'a, T, O: Debug + 'static, E: ToOwned<Owned = O>> Throw for &'a Result<T, E> {
     type Out = &'a T;
 
-    fn throw(self, cx: &ScopeState) -> Option<Self::Out> {
+    fn throw(self) -> Option<Self::Out> {
         match self {
             Ok(t) => Some(t),
-            Err(e) => {
-                cx.throw(e.to_owned());
-                None
-            }
+            Err(e) => throw_error(e.to_owned()),
         }
     }
 
-    fn throw_with<D: Debug + 'static>(
-        self,
-        cx: &ScopeState,
-        err: impl FnOnce() -> D,
-    ) -> Option<Self::Out> {
+    fn throw_with<D: Debug + 'static>(self, err: impl FnOnce() -> D) -> Option<Self::Out> {
         match self {
             Ok(t) => Some(t),
-            Err(_e) => {
-                cx.throw(err());
-                None
-            }
+            Err(_e) => throw_error(err()),
         }
     }
 }
@@ -154,25 +237,15 @@ impl<'a, T, O: Debug + 'static, E: ToOwned<Owned = O>> Throw for &'a Result<T, E
 impl<T, E: Debug + 'static> Throw for Result<T, E> {
     type Out = T;
 
-    fn throw(self, cx: &ScopeState) -> Option<T> {
+    fn throw(self) -> Option<T> {
         match self {
             Ok(t) => Some(t),
-            Err(e) => {
-                cx.throw(e);
-                None
-            }
+            Err(e) => throw_error(e),
         }
     }
 
-    fn throw_with<D: Debug + 'static>(
-        self,
-        cx: &ScopeState,
-        error: impl FnOnce() -> D,
-    ) -> Option<Self::Out> {
-        self.ok().or_else(|| {
-            cx.throw(error());
-            None
-        })
+    fn throw_with<D: Debug + 'static>(self, error: impl FnOnce() -> D) -> Option<Self::Out> {
+        self.ok().or_else(|| throw_error(error()))
     }
 }
 
@@ -180,21 +253,234 @@ impl<T, E: Debug + 'static> Throw for Result<T, E> {
 impl<T> Throw for Option<T> {
     type Out = T;
 
-    fn throw(self, cx: &ScopeState) -> Option<T> {
-        self.or_else(|| {
-            cx.throw("None error.");
-            None
-        })
+    fn throw(self) -> Option<T> {
+        self.or_else(|| throw_error("Attempted to unwrap a None value."))
     }
 
-    fn throw_with<D: Debug + 'static>(
+    fn throw_with<D: Debug + 'static>(self, error: impl FnOnce() -> D) -> Option<Self::Out> {
+        self.or_else(|| throw_error(error()))
+    }
+}
+
+pub struct ErrorHandler<'a>(Box<dyn Fn(CapturedError) -> LazyNodes<'a, 'a> + 'a>);
+impl<'a, F: Fn(CapturedError) -> LazyNodes<'a, 'a> + 'a> From<F> for ErrorHandler<'a> {
+    fn from(value: F) -> Self {
+        Self(Box::new(value))
+    }
+}
+fn default_handler<'a>(error: CapturedError) -> LazyNodes<'a, 'a> {
+    LazyNodes::new(move |__cx: &ScopeState| -> VNode {
+        static TEMPLATE: Template = Template {
+            name: "error_handle.rs:42:5:884",
+            roots: &[TemplateNode::Element {
+                tag: "pre",
+                namespace: None,
+                attrs: &[TemplateAttribute::Static {
+                    name: "color",
+                    namespace: Some("style"),
+                    value: "red",
+                }],
+                children: &[TemplateNode::DynamicText { id: 0usize }],
+            }],
+            node_paths: &[&[0u8, 0u8]],
+            attr_paths: &[],
+        };
+        VNode {
+            parent: None,
+            key: None,
+            template: std::cell::Cell::new(TEMPLATE),
+            root_ids: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(),
+            dynamic_nodes: __cx
+                .bump()
+                .alloc([__cx.text_node(format_args!("{0}", error))]),
+            dynamic_attrs: __cx.bump().alloc([]),
+        }
+    })
+}
+pub struct ErrorBoundaryProps<'a> {
+    children: Element<'a>,
+    handle_error: ErrorHandler<'a>,
+}
+impl<'a> ErrorBoundaryProps<'a> {
+    /**
+    Create a builder for building `ErrorBoundaryProps`.
+    On the builder, call `.children(...)`(optional), `.handle_error(...)`(optional) to set the values of the fields.
+    Finally, call `.build()` to create the instance of `ErrorBoundaryProps`.
+                        */
+    #[allow(dead_code)]
+    pub fn builder() -> ErrorBoundaryPropsBuilder<'a, ((), ())> {
+        ErrorBoundaryPropsBuilder {
+            fields: ((), ()),
+            _phantom: ::core::default::Default::default(),
+        }
+    }
+}
+#[must_use]
+#[doc(hidden)]
+#[allow(dead_code, non_camel_case_types, non_snake_case)]
+pub struct ErrorBoundaryPropsBuilder<'a, TypedBuilderFields> {
+    fields: TypedBuilderFields,
+    _phantom: ::core::marker::PhantomData<&'a ()>,
+}
+impl<'a, TypedBuilderFields> Clone for ErrorBoundaryPropsBuilder<'a, TypedBuilderFields>
+where
+    TypedBuilderFields: Clone,
+{
+    fn clone(&self) -> Self {
+        Self {
+            fields: self.fields.clone(),
+            _phantom: ::core::default::Default::default(),
+        }
+    }
+}
+impl<'a> Properties for ErrorBoundaryProps<'a> {
+    type Builder = ErrorBoundaryPropsBuilder<'a, ((), ())>;
+    const IS_STATIC: bool = false;
+    fn builder() -> Self::Builder {
+        ErrorBoundaryProps::builder()
+    }
+    unsafe fn memoize(&self, _: &Self) -> bool {
+        false
+    }
+}
+#[doc(hidden)]
+#[allow(dead_code, non_camel_case_types, non_snake_case)]
+pub trait ErrorBoundaryPropsBuilder_Optional<T> {
+    fn into_value<F: FnOnce() -> T>(self, default: F) -> T;
+}
+impl<T> ErrorBoundaryPropsBuilder_Optional<T> for () {
+    fn into_value<F: FnOnce() -> T>(self, default: F) -> T {
+        default()
+    }
+}
+impl<T> ErrorBoundaryPropsBuilder_Optional<T> for (T,) {
+    fn into_value<F: FnOnce() -> T>(self, _: F) -> T {
+        self.0
+    }
+}
+#[allow(dead_code, non_camel_case_types, missing_docs)]
+impl<'a, __handle_error> ErrorBoundaryPropsBuilder<'a, ((), __handle_error)> {
+    pub fn children(
         self,
-        cx: &ScopeState,
-        error: impl FnOnce() -> D,
-    ) -> Option<Self::Out> {
-        self.or_else(|| {
-            cx.throw(error());
-            None
-        })
+        children: Element<'a>,
+    ) -> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
+        let children = (children,);
+        let (_, handle_error) = self.fields;
+        ErrorBoundaryPropsBuilder {
+            fields: (children, handle_error),
+            _phantom: self._phantom,
+        }
+    }
+}
+#[doc(hidden)]
+#[allow(dead_code, non_camel_case_types, non_snake_case)]
+pub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_children {}
+#[doc(hidden)]
+#[allow(dead_code, non_camel_case_types, missing_docs)]
+impl<'a, __handle_error> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
+    #[deprecated(note = "Repeated field children")]
+    pub fn children(
+        self,
+        _: ErrorBoundaryPropsBuilder_Error_Repeated_field_children,
+    ) -> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
+        self
+    }
+}
+#[allow(dead_code, non_camel_case_types, missing_docs)]
+impl<'a, __children> ErrorBoundaryPropsBuilder<'a, (__children, ())> {
+    pub fn handle_error(
+        self,
+        handle_error: impl ::core::convert::Into<ErrorHandler<'a>>,
+    ) -> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
+        let handle_error = (handle_error.into(),);
+        let (children, _) = self.fields;
+        ErrorBoundaryPropsBuilder {
+            fields: (children, handle_error),
+            _phantom: self._phantom,
+        }
+    }
+}
+#[doc(hidden)]
+#[allow(dead_code, non_camel_case_types, non_snake_case)]
+pub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error {}
+#[doc(hidden)]
+#[allow(dead_code, non_camel_case_types, missing_docs)]
+impl<'a, __children> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
+    #[deprecated(note = "Repeated field handle_error")]
+    pub fn handle_error(
+        self,
+        _: ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error,
+    ) -> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
+        self
+    }
+}
+#[allow(dead_code, non_camel_case_types, missing_docs)]
+impl<
+        'a,
+        __handle_error: ErrorBoundaryPropsBuilder_Optional<ErrorHandler<'a>>,
+        __children: ErrorBoundaryPropsBuilder_Optional<Element<'a>>,
+    > ErrorBoundaryPropsBuilder<'a, (__children, __handle_error)>
+{
+    pub fn build(self) -> ErrorBoundaryProps<'a> {
+        let (children, handle_error) = self.fields;
+        let children = ErrorBoundaryPropsBuilder_Optional::into_value(children, || {
+            ::core::default::Default::default()
+        });
+        let handle_error = ErrorBoundaryPropsBuilder_Optional::into_value(handle_error, || {
+            ErrorHandler(Box::new(default_handler))
+        });
+        ErrorBoundaryProps {
+            children,
+            handle_error,
+        }
+    }
+}
+/// Create a new error boundary component.
+///
+/// ## Details
+///
+/// Error boundaries handle errors within a specific part of your application. Any errors passed in a child with [`Throw`] will be caught by the nearest error boundary.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// rsx!{
+///     ErrorBoundary {
+///         handle_error: |error| rsx! { "Oops, we encountered an error. Please report {error} to the developer of this application" }
+///         ThrowsError {}
+///     }
+/// }
+/// ```
+///
+/// ## Usage
+///
+/// Error boundaries are an easy way to handle errors in your application.
+/// They are similar to `try/catch` in JavaScript, but they only catch errors in the tree below them.
+/// Error boundaries are quick to implement, but it can be useful to individually handle errors in your components to provide a better user experience when you know that an error is likely to occur.
+#[allow(non_upper_case_globals, non_snake_case)]
+pub fn ErrorBoundary<'a>(cx: Scope<'a, ErrorBoundaryProps<'a>>) -> Element {
+    let error_boundary = use_error_boundary(cx);
+    match error_boundary.take_error() {
+        Some(error) => cx.render((cx.props.handle_error.0)(error)),
+        None => Some({
+            let __cx = cx;
+            static TEMPLATE: Template = Template {
+                name: "examples/error_handle.rs:81:17:2342",
+                roots: &[TemplateNode::Dynamic { id: 0usize }],
+                node_paths: &[&[0u8]],
+                attr_paths: &[],
+            };
+            VNode {
+                parent: None,
+                key: None,
+                template: std::cell::Cell::new(TEMPLATE),
+                root_ids: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(),
+                dynamic_nodes: __cx.bump().alloc([{
+                    let ___nodes = (&cx.props.children).into_vnode(__cx);
+                    ___nodes
+                }]),
+                dynamic_attrs: __cx.bump().alloc([]),
+            }
+        }),
     }
 }

+ 5 - 4
packages/core/src/lib.rs

@@ -90,10 +90,11 @@ pub mod prelude {
     pub use crate::innerlude::{
         consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context,
         provide_context, provide_context_to_scope, provide_root_context, push_future,
-        remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue,
-        Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, IntoDynNode,
-        LazyNodes, Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId,
-        Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
+        remove_future, schedule_update_any, spawn, spawn_forever, suspend, use_error_boundary,
+        AnyValue, Component, Element, ErrorBoundary, Event, EventHandler, Fragment,
+        IntoAttributeValue, IntoDynNode, LazyNodes, Properties, Runtime, RuntimeGuard, Scope,
+        ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw,
+        VNode, VirtualDom,
     };
 }
 

+ 1 - 20
packages/core/src/scope_context.rs

@@ -1,5 +1,5 @@
 use crate::{
-    innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
+    innerlude::{Scheduler, SchedulerMsg},
     runtime::{with_current_scope, with_runtime},
     Element, ScopeId, TaskId,
 };
@@ -7,7 +7,6 @@ use rustc_hash::FxHashSet;
 use std::{
     any::Any,
     cell::{Cell, RefCell},
-    fmt::Debug,
     future::Future,
     rc::Rc,
     sync::Arc,
@@ -250,19 +249,6 @@ impl ScopeContext {
         self.tasks.remove(id);
     }
 
-    /// Inject an error into the nearest error boundary and quit rendering
-    ///
-    /// The error doesn't need to implement Error or any specific traits since the boundary
-    /// itself will downcast the error into a trait object.
-    pub fn throw(&self, error: impl Debug + 'static) -> Option<()> {
-        if let Some(cx) = self.consume_context::<Rc<ErrorBoundary>>() {
-            cx.insert_error(self.scope_id(), Box::new(error));
-        }
-
-        // Always return none during a throw
-        None
-    }
-
     /// Mark this component as suspended and then return None
     pub fn suspend(&self) -> Option<Element> {
         self.suspended.set(true);
@@ -332,11 +318,6 @@ pub fn suspend() -> Option<Element<'static>> {
     None
 }
 
-/// Throw an error into the nearest error boundary
-pub fn throw(error: impl Debug + 'static) -> Option<()> {
-    with_current_scope(|cx| cx.throw(error)).flatten()
-}
-
 /// Pushes the future onto the poll queue to be polled after the component renders.
 pub fn push_future(fut: impl Future<Output = ()> + 'static) -> Option<TaskId> {
     with_current_scope(|cx| cx.push_future(fut))

+ 0 - 14
packages/core/src/scopes.rs

@@ -2,7 +2,6 @@ use crate::{
     any_props::AnyProps,
     any_props::VProps,
     bump_frame::BumpFrame,
-    innerlude::ErrorBoundary,
     innerlude::{DynamicNode, EventHandler, VComponent, VText},
     lazynodes::LazyNodes,
     nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
@@ -506,19 +505,6 @@ impl<'src> ScopeState {
         AttributeValue::Any(RefCell::new(Some(boxed)))
     }
 
-    /// Inject an error into the nearest error boundary and quit rendering
-    ///
-    /// The error doesn't need to implement Error or any specific traits since the boundary
-    /// itself will downcast the error into a trait object.
-    pub fn throw(&self, error: impl Debug + 'static) -> Option<()> {
-        if let Some(cx) = self.consume_context::<Rc<ErrorBoundary>>() {
-            cx.insert_error(self.scope_id(), Box::new(error));
-        }
-
-        // Always return none during a throw
-        None
-    }
-
     /// Mark this component as suspended and then return None
     pub fn suspend(&self) -> Option<Element> {
         let cx = self.context();

+ 5 - 2
packages/core/src/virtual_dom.rs

@@ -16,7 +16,7 @@ use crate::{
 use futures_util::{pin_mut, StreamExt};
 use rustc_hash::{FxHashMap, FxHashSet};
 use slab::Slab;
-use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
+use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc, sync::Arc};
 
 /// A virtual node system that progresses user events and diffs UI trees.
 ///
@@ -273,7 +273,10 @@ impl VirtualDom {
         );
 
         // Unlike react, we provide a default error boundary that just renders the error as a string
-        root.provide_context(Rc::new(ErrorBoundary::new(ScopeId::ROOT)));
+        root.provide_context(Rc::new(ErrorBoundary::new_in_scope(
+            ScopeId::ROOT,
+            Arc::new(|_| {}),
+        )));
 
         // the root element is always given element ID 0 since it's the container for the entire tree
         dom.elements.insert(ElementRef::none());