Skip to main content
A render error in React unmounts the whole tree and leaves a blank page. @cskn/ui catches it for you and shows a recovery screen instead. UIProvider mounts a boundary at the root, so every app has one without doing anything.

What the root boundary does

The boundary sorts each crash into one of three kinds and handles each differently: The screen is in English or Turkish, following the shell language. It is translated on its own, so it still works if the crash took your app’s i18n down with it.

Keeping a crash local

The root boundary replaces the whole app. Add your own AppErrorBoundary around a part of the screen so a crash there leaves the header and tab bar working.
unknown[]
Clear the error when any of these change. Pass the route so leaving the broken screen recovers it without a reload.
'app' | 'section'
default:"'section'"
section sits inside your own layout. app fills the viewport and offers to close the app in a shell; the root boundary uses it.
ReactNode | (props: ErrorFallbackProps) => ReactNode
Replace the default screen. A function receives error, kind, scope, reset() and reload().
Partial<ErrorFallbackLabels>
Override the default screen’s text, for example with your app’s own translations: renderTitle, renderBody, chunkTitle, chunkBody, networkTitle, offlineTitle, networkBody, retry, reload, close, details.
string
Force the default screen’s language. Defaults to the shell language.
(report: AppErrorReport) => void
Called after the crash is reported.
To customize the root boundary, pass the same props to UIProvider: <UIProvider errorBoundary={{ labels, onError }}>. Pass errorBoundary={false} if you mount your own at the root. ErrorFallback is the default screen as a component, for when you want to render it yourself.

A query that failed to load

A crash is different from a request that didn’t load. For the second, render ErrorState where the content would be. It looks different from an empty state on purpose: “nothing here yet” and “this didn’t load” ask the user for opposite things.
unknown
The failure. When it says the user hasn’t turned your app on (403 APP_NOT_ACTIVATED), the state says so and hides the retry button, since retrying can’t fix that.
() => void
Shows a retry button. Re-run the query rather than reloading the page, so anything playing keeps playing.
IconName | ReactNode
Icon above the title.
ButtonVariant
default:"'ghost'"
Retry button style.
Partial<{ title, description, retry, notActivatedTitle, notActivatedDescription }>
Text overrides.
string
Force English or Turkish.

Listening for errors

Every crash a boundary catches, every uncaught error and every unhandled promise rejection is logged and passed to onAppError listeners from @cskn/sdk. Use it to send crashes to your own error tracker.
'chunk' | 'network' | 'render'
The kind, as in the table above.
'app' | 'section' | 'global'
Which boundary caught it. global means no boundary saw it: a window error or an unhandled rejection.
string
React’s component stack, when a boundary caught it.
string
Your app’s slug and deployed version.
The SDK also exports the pieces the boundary is built from: reportAppError(error, { scope }) reports an error you caught yourself, classifyAppError(error) returns its kind, and reloadAppOnce() reloads unless it already did in the last 15 seconds. UIProvider installs the global handlers; without it, call installAppErrorHandlers() once at startup.