Skip to main content

useErrorBoundary

useErrorBoundary enables components to catch and handle errors higher up in the tree and provide appropriate fallback, logging or retry mechanisms.

A component becomes an error boundary when it declares a useErrorBoundary hook. The lambda passed to useErrorBoundary will receive any exceptions that are thrown while rendering or dispatching events of components sitting underneath the error boundary in the tree.

useErrorBoundary can be used in combination with useState, to update the state with the exception that was caught. This will then trigger a render pass with the new state value in order to replace the crashing component with an error message, or not display it at all.

Example: Showing an Error State in UI​

The following code shows how a component can handle an error in one of its child components and render an error state instead of crashing the app:

class KErrorBoundary(private val childComponent: Component) : KComponent() {

override fun ComponentScope.render(): Component? {
val errorState = useState<Exception?> { null }
useErrorBoundary { exception: Exception -> errorState.update(exception) }

errorState.value?.let {
return Column(style = Style.margin(all = 16.dp)) {
child(KDebugComponent(message = "KComponent's Error Boundary", throwable = it))
}
}

return childComponent
}
}

Re-throwing Errors​

An error boundary may choose to only handle certain classes of errors. You can re-throw an exception from within the useErrorBoundary hook so that it propagates up the component tree until it is either caught by another error boundary or hits the root and causes a crash. This is done by calling ComponentUtils.raise with your context and the exception.

Why not Try/Catch?​

You may be asking why all this additional infrastructure is necessary when you could just use try/catch. The problem is that there's no easily accessible place to wrap your component code using try/catch.

The following snippet shows the use of try/catch:

class KIncorrectErrorHandlingComponent : KComponent() {

override fun ComponentScope.render(): Component {
// This implementation won't work - you need to implement error boundary with useErrorBoundary
// hook instead.
return try {
// PossiblyCrashingSubTitleComponent can crash
PossiblyCrashingSubTitleComponent.create(context).subtitle("example").build()
} catch (e: Exception) {
// error handling code
KDebugComponent(message = "Error Component", throwable = e)
}
}
}

When using try/catch (as in the above code), if PossiblyCrashingSubTitleComponent throws an exception in onCreateLayout, it wouldn't be caught! The reason for this is that you are just returning a declaration of your layout here and don't actually execute any code.

This is the responsibility of the Litho framework, hence the need to provide higher-level infrastructure to give access to those errors.