dioxus icon indicating copy to clipboard operation
dioxus copied to clipboard

Allowing ErrorBoundary to work smoothly with Error

Open chunleng opened this issue 9 months ago • 3 comments

Feature Request

I gave ErrorBoundary a try according to the guide. However, when I try use it with some errors, I realize that it might be a little hard to get it to play nicely with the errors. So for example:

use dioxus::prelude::*;
use thiserror::Error;

fn main() {
    dioxus::launch(|| {
        rsx! {
            ErrorBoundary {
                handle_error: |e: ErrorContext| { rsx! {
                    {e.show()}
                } },
                Foo {}
            }
        }
    });
}

#[component]
fn Foo() -> Element {
    Err(AppError::SomeError).show(|e| rsx! {{e.to_string()}})?;
    rsx! {}
}

#[derive(Error, Debug)]
enum AppError {
    #[error("Some error has occurred")]
    SomeError,
}

In order to show the error message in the error, it seems that I have to add .show(|e| rsx! {{e.to_string()}}) to every Result carrying the error. This is a little of a hassle and I thought there should be a better way.

Implement Suggestion

Method 1

Maybe, make CaptureError capture information about the original error. (Maybe we can downcast? Didn't seem to work in my case and rather than downcast, it's better to work with the actual error)

            ErrorBoundary {
                handle_error: |e: ErrorContext| { rsx! {
                    for x in e.errors().iter().map(|e| { e.error.to_string() }){
                        {x}
                    }
                } },
                Foo {}
            }

Method 2

Allow a way for user to implement a trait that can override Into<CapturedError> trait. For example:

impl Into<CapturedError> for AppError {
    fn into(self) -> CapturedError {
        Err::<(), _>(self)
            .show(|x| {
                rsx! {
                    {x.to_string()}
                }
            })
            .unwrap_err()
    }
}

chunleng avatar May 20 '25 13:05 chunleng

I am more in favor of method 2 as it provide more flexibility, in case of Apps with more well-managed error, we can create an Into trait implementation that automatically use App level error, such as:

#[derive(Error, Debug)]
enum AppError {
    #[error("Error 1-{0}")]
    SomeError(#[from] DisplayError),
}

#[derive(Error, Debug)]
enum DisplayError {
    #[error("1")]
    Unknown
}

impl Into<CapturedError> for DisplayError {
    fn into(self) -> CapturedError {
        Err::<(), _>(self)
            .show(|x| {
                rsx! {
                    {AppError::from(x).to_string()} // <- Make DisplayError into AppError before returning the display format
                }
            })
            .unwrap_err()
    }
}

chunleng avatar May 20 '25 13:05 chunleng

Captured error has a default from implementation for any type that implements Error. You can display the error by formatting it like this:


use dioxus::prelude::*;
use thiserror::Error;

fn main() {
    dioxus::launch(|| {
        rsx! {
            ErrorBoundary {
                handle_error: |e: ErrorContext| { rsx! {
                    for error in e.errors() {
                        p { "{error}" }
                    }
                } },
                Foo {}
            }
        }
    });
}

#[component]
fn Foo() -> Element {
    Err(AppError::SomeError)?;
    rsx! {}
}

#[derive(Error, Debug)]
enum AppError {
    #[error("Some error has occurred")]
    SomeError,
}

You can also downcast errors like this:


use dioxus::prelude::*;
use thiserror::Error;

fn main() {
    dioxus::launch(|| {
        rsx! {
            ErrorBoundary {
                handle_error: |e: ErrorContext| { rsx! {
                    for e in e.errors() {
                        {
                            if let Some(my_error) = e.downcast::<AppError>() {
                                rsx! { div { "App Error {my_error}" } }
                            } else {
                                rsx! { div { "{e}" } }
                            }
                        }
                    }
                } },
                Foo {}
            }
        }
    });
}

#[component]
fn Foo() -> Element {
    Err(AppError::SomeError)?;
    rsx! {}
}

#[derive(Error, Debug)]
enum AppError {
    #[error("Some error has occurred")]
    SomeError,
}

We could expose a method to get the inner &dyn Error from the Captured error to give more flexibility in how it is displayed

ealmloff avatar May 20 '25 13:05 ealmloff

Thank you for the quick reply!

Captured error has a default from implementation for any type that implements Error. You can display the error by formatting it like this:

I understand the basic implementation, it's just hard to make it "play nicely" with the original errors because everything ends up as CapturedError which limits access to the original error and therefore also limits the formatting of error.

if let Some(my_error) = e.downcast::<AppError>() {
                                rsx! { div { "App Error {my_error}" } }

Downcasting does not seem to get into this branch on this code

We could expose a method to get the inner &dyn Error from the Captured error to give more flexibility in how it is displayed

I guess it will help a great deal if an owned error can be passed as it will help with the handling in the structure Error case mention here

chunleng avatar May 20 '25 14:05 chunleng

This works now thanks to the overhaul of the rror system.

use dioxus::prelude::*;

fn main() {
    dioxus::launch(app);
}

fn app() -> Element {
    rsx! {
        ErrorBoundary {
            handle_error: move |error: ErrorContext| {
                //
                let t = error.error().unwrap();
                t.downcast_ref::<AppError>().unwrap();

                rsx! {
                    "yay"
                }
            },
            MakeError {}
        }
    }
}

fn MakeError() -> Element {
    // just use `?` for returns
    Err(AppError::SomeError)?;

    rsx! {
        "noo"
    }
}

use thiserror::Error;
#[derive(Error, Debug)]
enum AppError {
    #[error("Some error has occurred")]
    SomeError,
}

jkelleyrtp avatar Nov 11 '25 19:11 jkelleyrtp