Error Handling Usage
Overview
The tooling provided by this package is intended to help manage errors within the Platform Framework in a way that is most helpful to users. This applies to core functionality as well as specific feature packages.
Many of our feature packages, such as List Views and Record Pages, already implement error boundaries using this tooling, and provide good examples of how you might approach it elsewhere.
Remember that error boundaries are hierarchical - Errors are always caught by the closest ancestor boundary. This allows you to target more specific parts of the component tree the further you nest boundaries.
An error boundary will automatically catch component lifecycle errors from their descendants. Side effects like hooks, click handlers and async data fetching are not automatically caught and usually have to be passed to useErrorHander() to pull them into the context of the closest ancestor boundary.
platform-errors provides two main implementation methods - Within Horizon components (offering the most flexibility), or within templates.
How to implement within Horizon
Implementing error boundaries and fallbacks is made much easier by using our presets, which significantly reduce the amount of boilerplate required per boundary and ensure consistency across the Platform Framework. You should find they cater to most needs out of the box.
This tooling provides boundaries with accompanying fallbacks at preset error levels. They are available as components and higher-order components depending on your needs.
Should I use a component or higher-order component?
- Use a component when you want to wrap one or more components from a parent component, and don't want or need to include the parent's logic in it - Perhaps that is the responsibility of another boundary further up the tree.
- The boundary will catch errors from all components it wraps.
- If the parent component is not used (by modifying a template) then the error boundary will not exist.
- Use a higher-order component when you want to wrap a specific component, including all of its own lifecycle logic. The component itself becomes a boundary.
- The boundary will catch errors from all components it wraps, including itself.
- Anywhere the component is used it will include the error boundary.
ObjectErrorBoundary, GroupErrorBoundary and ComponentErrorBoundary components
Use these like any other React component by wrapping them around the components you want to catch errors from. The component that implements the boundary is not part of it, so will not have its own errors caught.
These components also accept optional props:
message: Set a custom message to be shown in the fallbackonError: Set a custom callback to fire on every error
import { GroupErrorBoundary } from '@skedulo/platform-errors'
const ImplementsTheBoundary = () => {
// ...
// some lifecycle logic
// all outside the boundary
// ...
someFunctionThatThrows() // won't be caught
return (
<>
<OutsideTheBoundary />
<GroupErrorBoundary>
<InsideTheBoundary />
<AlsoInsideTheBoundary />
</GroupErrorBoundary>
</>
)
}
withObjectErrorBoundary, withGroupErrorBoundary and withComponentErrorBoundary higher-order components
Wrap the HOC around the target component declaration. The component essentially becomes a boundary itself, so all of its own errors will be caught as well as those bubbling up from its descendants - unless there is another boundary below!
These HOCs also accept an options object as the second arg, i.e. withGroupErrorBoundary(Component, options):
options.message: Set a custom message to be shown in the fallbackoptions.onError: Set a custom callback to fire on every error
import { withGroupErrorBoundary } from '@skedulo/platform-errors'
const BecomesTheErrorBoundary =
withGroupErrorBoundary<BecomesTheErrorBoundaryProps>((props) => {
// ...
// some lifecycle logic
// all inside the boundary
// ...
someFunctionThatThrows() // will be caught
return (
<>
<InsideTheBoundary />
<ComponentErrorBoundary>
<InsideAnotherErrorBoundary />
</ComponentErrorBoundary>
</>
)
})
Handling errors caused by side effects
With components inside a boundary, if they fetch data or perform other side effects that may cause errors, these need to be handled separately to ensure they are caught.
Here's an example of handling a data fetching error. The error returned by the API will be passed along to a generic handler that will try to "Platform-ify" the error.
import { useErrorHandler, handlePlatformError } from '@skedulo/platform-errors'
const InsideTheBoundary = () => {
const { result, error } = useAsync(async () => {
try {
await someApiRequest()
} catch (error) {
handlePlatformError(error)
}
}, [])
useErrorHandler(error) // throws the error within the boundary context
return <>some sort of layout with {result}</>
}
Click handlers and other events that might throw errors also need to be handled appropriately. When you can't rely on useful context being supplied by an API error, throwPlatformError() is usually what you want, as it allows you to provide that additional context directly, such as error codes and trace IDs.
import { throwPlatformError } from '@skedulo/platform-errors'
const InsideTheBoundary = () => {
const handleClick = async () => {
try {
await somethingThatMightThrow()
handleTheHappyPath()
} catch (error) {
throwPlatformError(error.message, 'BADNESS', 'trace-id-123')
}
}
return <Button onClick={handleClick}>Click to throw (maybe)</Button>
}
How to implement within templates [🧪 EXPERIMENTAL]
Implementing error boundaries within templates offers limited functionality. However, it can be useful for catching errors from Platform Components in specific layouts that may otherwise cause a more obnoxious fallback in higher-level boundary.
<platform-error-boundary> is a Web Component that serves as a proxy to a real React error boundary. The Web Component itself contains no logic - it simply passes on some attributes and child nodes to a DOM parser so that it can be rendered in React.
Use it by wrapping around Platform Components. Currently, it can only catch errors from implementations of <platform-component> in templates.
It accepts some optional attributes:
message: Set a custom message to be shown in the fallbacklevel: Set the error level for the fallback. Defaults to5(Component Error)
<platform-error-boundary>
<platform-component
package-name="recordpage"
name="RecordDefiner"
></platform-component>
</platform-error-boundary>
This component is a work in progress and should be considered experimental if you choose to use it. Implementing templated boundaries in this way will feel slightly disconnected and flaky, because the Platform Components they wrap could already have error boundaries of their own that you won't know about, meaning that errors could be caught before ever reaching a templated boundary.