> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cskn.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Error handling

> Crash boundaries, the failed-load state, and listening for app errors.

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:

| Kind | Cause | What happens |
| - | - | - |
| `chunk` | A lazily loaded chunk is gone because a new version was deployed while the app was open | The page reloads once on its own. If it fails again within 15 seconds, the user gets a "new version is ready" screen with a reload button instead of a loop. |
| `network` | A request failed without a response, or the device is offline | A "couldn't connect" screen. It recovers by itself when the device comes back online. |
| `render` | Anything else | A "something went wrong" screen with **Try again** (render again) and **Reload**. In a shell it also offers **Close app**. |

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.

```tsx theme={null}
import { useLocation, Routes } from 'react-router-dom';
import { AppErrorBoundary } from '@cskn/ui';

function Shell() {
  const { pathname } = useLocation();
  return (
    <>
      <Header />
      <AppErrorBoundary resetKeys={[pathname]}>
        <Routes>{/* … */}</Routes>
      </AppErrorBoundary>
      <TabBar />
    </>
  );
}
```

**resetKeys** `unknown[]`

Clear the error when any of these change. Pass the route so leaving the broken screen recovers it without a reload.

**scope** `'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.

**fallback** `ReactNode | (props: ErrorFallbackProps) =\> ReactNode`

Replace the default screen. A function receives `error`, `kind`, `scope`, `reset()` and `reload()`.

**labels** `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`.

**locale** `string`

Force the default screen's language. Defaults to the shell language.

**onError** `(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.

```tsx theme={null}
import { ErrorState, EmptyState } from '@cskn/ui';

const { data, error, refetch } = useQuery(/* … */);

if (error) return <ErrorState error={error} onRetry={() => refetch()} />;
if (!data.length) return <EmptyState title="No books yet" />;
```

**error** `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.

**onRetry** `() =\> void`

Shows a retry button. Re-run the query rather than reloading the page, so anything playing keeps playing.

**icon** `IconName | ReactNode`

Icon above the title.

**retryVariant** `ButtonVariant` (default: 'ghost')

Retry button style.

**labels** `Partial\<\{ title, description, retry, notActivatedTitle, notActivatedDescription \}\>`

Text overrides.

**locale** `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.

```tsx theme={null}
import { onAppError } from '@cskn/sdk';

const unsubscribe = onAppError(({ error, kind, scope, componentStack, slug, version }) => {
  myTracker.capture(error, { kind, scope, componentStack, slug, version });
});
```

**kind** `'chunk' | 'network' | 'render'`

The kind, as in the table above.

**scope** `'app' | 'section' | 'global'`

Which boundary caught it. `global` means no boundary saw it: a window error or an unhandled rejection.

**componentStack** `string`

React's component stack, when a boundary caught it.

**slug, version** `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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.