Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Error Handling

WaterUI does not force a bespoke error type. Instead, it lets you turn any std::error::Error into a renderable view via waterui::error::ErrorView, and customize how errors look using environment hooks.

From Errors to Views

Wrap errors with ErrorView::from whenever a Result fails:

#![allow(unused)]
fn main() {
use waterui::prelude::*;
use waterui::error::ErrorView;

fn user_profile(id: u64) -> impl View {
    match load_user(id) {
        Ok(user) => profile_card(user),
        Err(err) => ErrorView::from(err),
    }
}
}

ErrorView implements View, so you can drop it anywhere inside a stack or navigation view. The default renderer simply prints the error via Display.

Customizing the Presentation

Inject a ErrorViewBuilder into the environment to override how errors render globally:

#![allow(unused)]
fn main() {
use waterui::error::ErrorViewBuilder;

let env = Environment::new().with(ErrorViewBuilder::new(|err, _env| {
    AnyView::new(
        vstack((
            text!("⚠️ Something went wrong").bold(),
            text!("{err}").foreground(Color::srgb(200, 80, 80)),
            button("Retry").action(|| task(retry_last_request())),
        ))
        .padding()
    )
}));

app_root().with_env(env)
}

Now every ErrorView produced inside the tree uses this layout automatically.

Inline Result Helpers

If you prefer chaining, build a helper that maps Result<T, E> into either a view or an ErrorView:

#![allow(unused)]
fn main() {
fn result_view<T, E>(result: Result<T, E>, render: impl FnOnce(T) -> AnyView) -> AnyView
where
    E: std::error::Error + 'static,
{
    match result {
        Ok(value) => render(value),
        Err(err) => AnyView::new(ErrorView::from(err)),
    }
}
}

Use it when composing lists or complex layouts so you do not repeat match expressions everywhere.

Contextual Actions

Because error builders receive the Environment, you can extract services (analytics, retry queues, offline caches) with extract::Use<T> just like button handlers. A typical pattern:

#![allow(unused)]
fn main() {
ErrorViewBuilder::new(|err, env| {
    let telemetry = env.get::<Telemetry>().cloned();
    if let Some(t) = telemetry {
        t.record_error(&err);
    }
    AnyView::new(text!("{err}"))
})
}

Pairing with Suspense

When fetching data asynchronously, wrap the result inside Suspense and convert failures into ErrorView instances. Users get a consistent loading/error pipeline without sprinkling Result logic throughout the UI.

Consistent, informative error displays keep apps trustworthy. Centralize styling via ErrorViewBuilder and lean on ErrorView::from wherever fallible operations occur.