diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index 2311cfd62..8f0dfe201 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -268,6 +268,7 @@ impl ErrorContext { /// Clear all errors from this Error Boundary pub fn clear_errors(&self) { self.errors.borrow_mut().clear(); + self.id.needs_update(); } } @@ -696,33 +697,46 @@ impl< /// /// ## Details /// -/// Error boundaries handle errors within a specific part of your application. Any errors passed up from a child will be caught by the nearest error boundary. +/// Error boundaries handle errors within a specific part of your application. They are similar to `try/catch` in JavaScript, but they only catch errors in the tree below them. +/// Any errors passed up from a child will be caught by the nearest error boundary. 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. /// /// ## Example /// /// ```rust, no_run -/// # use dioxus::prelude::*; +/// use dioxus::prelude::*; +/// /// fn App() -> Element { +/// let mut multiplier = use_signal(|| String::from("2")); /// rsx! { +/// input { +/// r#type: "text", +/// value: multiplier, +/// oninput: move |e| multiplier.set(e.value()) +/// } /// ErrorBoundary { -/// handle_error: |errors: ErrorContext| rsx! { "Oops, we encountered an error. Please report {errors:?} to the developer of this application" }, +/// handle_error: |errors: ErrorContext| { +/// rsx! { +/// div { +/// "Oops, we encountered an error. Please report {errors:?} to the developer of this application" +/// } +/// } +/// }, /// Counter { -/// multiplier: "1234" +/// multiplier /// } /// } /// } /// } /// /// #[component] -/// fn Counter(multiplier: String) -> Element { -/// // You can bubble up errors with `?` inside components -/// let multiplier_parsed = multiplier.parse::()?; +/// fn Counter(multiplier: ReadOnlySignal) -> Element { +/// let multiplier_parsed = multiplier().parse::()?; /// let mut count = use_signal(|| multiplier_parsed); /// rsx! { /// button { -/// // Or inside event handlers /// onclick: move |_| { -/// let multiplier_parsed = multiplier.parse::()?; +/// let multiplier_parsed = multiplier().parse::()?; /// *count.write() *= multiplier_parsed; /// Ok(()) /// }, @@ -732,16 +746,69 @@ impl< /// } /// ``` /// -/// ## Usage +/// ## Resetting the error boundary /// -/// 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. +/// Once the error boundary catches an error, it will render the rsx returned from the handle_error function instead of the children. To reset the error boundary, +/// you can call the [`ErrorContext::clear_errors`] method. This will clear all errors and re-render the children. +/// +/// ```rust, no_run +/// # use dioxus::prelude::*; +/// fn App() -> Element { +/// let mut multiplier = use_signal(|| String::new()); +/// rsx! { +/// input { +/// r#type: "text", +/// value: multiplier, +/// oninput: move |e| multiplier.set(e.value()) +/// } +/// ErrorBoundary { +/// handle_error: |errors: ErrorContext| { +/// rsx! { +/// div { +/// "Oops, we encountered an error. Please report {errors:?} to the developer of this application" +/// } +/// button { +/// onclick: move |_| { +/// errors.clear_errors(); +/// }, +/// "try again" +/// } +/// } +/// }, +/// Counter { +/// multiplier +/// } +/// } +/// } +/// } +/// +/// #[component] +/// fn Counter(multiplier: ReadOnlySignal) -> Element { +/// let multiplier_parsed = multiplier().parse::()?; +/// let mut count = use_signal(|| multiplier_parsed); +/// rsx! { +/// button { +/// onclick: move |_| { +/// let multiplier_parsed = multiplier().parse::()?; +/// *count.write() *= multiplier_parsed; +/// Ok(()) +/// }, +/// "{count}x{multiplier}" +/// } +/// } +/// } +/// ``` #[allow(non_upper_case_globals, non_snake_case)] pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element { let error_boundary = use_hook(provide_error_boundary); let errors = error_boundary.errors(); - if errors.is_empty() { + let has_errors = !errors.is_empty(); + // Drop errors before running user code that might borrow the error lock + drop(errors); + + if has_errors { + (props.handle_error.0)(error_boundary.clone()) + } else { std::result::Result::Ok({ static TEMPLATE: Template = Template { roots: &[TemplateNode::Dynamic { id: 0usize }], @@ -755,9 +822,5 @@ pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element { Default::default(), ) }) - } else { - tracing::trace!("scope id: {:?}", current_scope_id()); - tracing::trace!("handling errors: {:?}", errors); - (props.handle_error.0)(error_boundary.clone()) } } diff --git a/packages/core/tests/error_boundary.rs b/packages/core/tests/error_boundary.rs index 53e209562..ed19de72e 100644 --- a/packages/core/tests/error_boundary.rs +++ b/packages/core/tests/error_boundary.rs @@ -1,6 +1,6 @@ #![allow(non_snake_case)] -use dioxus::prelude::*; +use dioxus::{prelude::*, CapturedError}; #[test] fn catches_panic() { @@ -30,3 +30,50 @@ fn ThrowChild() -> Element { rsx! { div {} } } + +#[test] +fn clear_error_boundary() { + static THREW_ERROR: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); + + #[component] + fn App() -> Element { + rsx! { + AutoClearError {} + } + } + + #[component] + pub fn ThrowsError() -> Element { + if THREW_ERROR.load(std::sync::atomic::Ordering::SeqCst) { + THREW_ERROR.store(true, std::sync::atomic::Ordering::SeqCst); + Err(CapturedError::from_display("This is an error").into()) + } else { + rsx! { + "We should see this" + } + } + } + + #[component] + pub fn AutoClearError() -> Element { + rsx! { + ErrorBoundary { + handle_error: |error: ErrorContext| { + error.clear_errors(); + + rsx! { + "We cleared it" + } + }, + + ThrowsError {} + } + } + } + + let mut dom = VirtualDom::new(App); + dom.rebuild(&mut dioxus_core::NoOpMutations); + let out = dioxus_ssr::render(&dom); + + assert_eq!(out, "We should see this"); +}