# React 18 and React 19

> Install @belvador/react for browser React apps (Vite, SPA), with separate wiring for React 18 and React 19.

Use `@belvador/react` for React apps that render in the browser — Vite, Create React App or any other single-page setup. It includes everything in `@belvador/browser`, plus an error boundary and React 19 root hooks. Lovable and TanStack Start apps use [TanStack Start](https://belvador.dev/en/docs/sdk/tanstack-start) instead.

## Before you start

- A project with platform **React (Vite / SPA)** and its DSN.
- React 18 or React 19. Check which one you have: `npm ls react`.

## 1. Install

```bash
npm install @belvador/react@0.6.1
```

## 2. Initialize

Create `src/instrument.ts`. It starts Belvador:

```ts title="src/instrument.ts"
import { init } from "@belvador/react";

init({
  dsn: "https://<public-key>@ingest.belvador.dev/<project-id>",
  release: import.meta.env.VITE_APP_VERSION, // your build or commit id
  environment: import.meta.env.MODE,
});
```

## 3. Import it first — React 18

Make `./instrument` the **first** import of `src/main.tsx`. Imports run before the rest of a file, so an `init` call written below `import App` never runs when one of App's modules fails while loading — a missing environment variable, for example. The page stays blank and nothing is reported. This version works with React 18 **and** 19. `ErrorBoundary` reports render errors (with the component stack) and shows the fallback instead of a blank page.

```tsx title="src/main.tsx"
import "./instrument"; // first line: starts Belvador before App's modules load
import { createRoot } from "react-dom/client";
import { ErrorBoundary } from "@belvador/react";

import App from "./App";

const root = document.getElementById("root");
if (!root) throw new Error("Root element missing");
createRoot(root).render(
  <ErrorBoundary fallback={<p>Something went wrong.</p>}>
    <App />
  </ErrorBoundary>,
);
```

## 3. Import it first — React 19

React 19 handles many errors itself and does not let them reach `window.onerror`. Pass `reactErrorHandler()` to the three root options so Belvador sees them. These options do not exist in React 18 — use the version above there.

```tsx title="src/main.tsx"
import "./instrument"; // first line: starts Belvador before App's modules load
import { createRoot } from "react-dom/client";
import { ErrorBoundary, reactErrorHandler } from "@belvador/react";

import App from "./App";

const root = document.getElementById("root");
if (!root) throw new Error("Root element missing");
createRoot(root, {
  onUncaughtError: reactErrorHandler(),
  onCaughtError: reactErrorHandler(),
  onRecoverableError: reactErrorHandler(),
}).render(
  <ErrorBoundary fallback={<p>Something went wrong.</p>}>
    <App />
  </ErrorBoundary>,
);
```

| Root option | Reports |
|---|---|
| `onUncaughtError` | Errors no error boundary caught |
| `onCaughtError` | Errors an error boundary caught and rendered a fallback for |
| `onRecoverableError` | Errors React recovered from on its own, for example hydration mismatches |

`reactErrorHandler` takes an optional callback that runs after reporting: `reactErrorHandler((error, errorInfo, eventId) => { … })`.

## 4. Allow the ingest address in your CSP

```text
connect-src 'self' https://ingest.belvador.dev;
```

Merge this into your existing `connect-src`; do not replace the policy.

## Error boundaries for parts of the page

Wrap risky parts of the UI so one failing widget does not take down the page:

```tsx
import { ErrorBoundary, withErrorBoundary } from "@belvador/react";

export function Dashboard() {
  return (
    <ErrorBoundary
      fallback={({ resetError }) => (
        <div role="alert">
          <p>This chart failed to load.</p>
          <button onClick={resetError}>Try again</button>
        </div>
      )}
    >
      <RevenueChart />
    </ErrorBoundary>
  );
}

// Or wrap a component once:
export const SafeRevenueChart = withErrorBoundary(RevenueChart, {
  fallback: <p>This chart failed to load.</p>,
});
```

`ErrorBoundary` reports each render error once and shows `fallback`. The user-feedback dialog some SDKs offer is not available: it would load a script from the network.

> [!NOTE]
> Routers with their own error screens — for example React Router's `errorElement` — catch errors before any boundary above them. Call `captureException(error)` inside that error screen, or wrap it with `ErrorBoundary`.

## Report errors you handle

Everything from `@belvador/browser` is exported here too:

```tsx
import { captureException, setUser } from "@belvador/react";

setUser({ id: user.id });

try {
  await importCsv(file);
} catch (error) {
  captureException(error);
  setStatus("failed");
}
```

## Check that it works

Add a temporary button, click it, and open the project in Belvador:

```tsx
function Crash(): never {
  throw new Error("Belvador React test");
}

export function TestButton() {
  const [crash, setCrash] = useState(false);
  return crash ? <Crash /> : <button onClick={() => setCrash(true)}>Send test error</button>;
}
```

The issue shows the component stack under **Contexts**. Remove the button afterwards.

## Limits to know

- Event handlers (`onClick`, …) and async code are not covered by error boundaries — React's rule. Errors there that nobody catches still reach Belvador through the global handlers; errors you catch need `captureException`.
- Server-side rendering frameworks other than TanStack Start are not covered by this package's setup.
