# React 18 và React 19

> Cài @belvador/react cho ứng dụng React chạy trên trình duyệt (Vite, SPA), với cách nối riêng cho React 18 và React 19.

Dùng `@belvador/react` cho ứng dụng React render trên trình duyệt — Vite, Create React App hoặc dạng SPA khác. Gói này có mọi thứ của `@belvador/browser`, cộng thêm error boundary và root hook của React 19. Ứng dụng Lovable và TanStack Start dùng [TanStack Start](https://belvador.dev/vi/docs/sdk/tanstack-start).

## Trước khi bắt đầu

- Một project có nền tảng **React (Vite / SPA)** và DSN của nó.
- React 18 hoặc React 19. Kiểm tra bản đang dùng: `npm ls react`.

## 1. Cài đặt

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

## 2. Khởi tạo

Tạo `src/instrument.ts`. File này khởi động 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 đầu tiên — React 18

Đặt `./instrument` là dòng import **đầu tiên** của `src/main.tsx`. Các import chạy trước phần còn lại của file, nên một lệnh `init` viết bên dưới `import App` sẽ không bao giờ chạy nếu một module của App lỗi lúc đang nạp — ví dụ thiếu biến môi trường. Trang trắng và không có lỗi nào được báo. Bản này chạy được với React 18 **và** 19. `ErrorBoundary` báo lỗi render (kèm component stack) và hiện nội dung thay thế thay vì trang trắng.

```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 đầu tiên — React 19

React 19 tự xử lý nhiều lỗi và không để chúng tới `window.onerror`. Truyền `reactErrorHandler()` vào ba tuỳ chọn của root để Belvador thấy các lỗi đó. Các tuỳ chọn này không có trong React 18 — ở React 18 dùng bản phía trên.

```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>,
);
```

| Tuỳ chọn của root | Báo lỗi gì |
|---|---|
| `onUncaughtError` | Lỗi không error boundary nào bắt |
| `onCaughtError` | Lỗi đã bị error boundary bắt và hiện nội dung thay thế |
| `onRecoverableError` | Lỗi React tự phục hồi được, ví dụ lệch dữ liệu khi hydrate |

`reactErrorHandler` nhận một callback tuỳ chọn, chạy sau khi đã báo: `reactErrorHandler((error, errorInfo, eventId) => { … })`.

## 4. Cho phép địa chỉ nhận lỗi trong CSP

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

Gộp dòng này vào `connect-src` hiện có; đừng thay cả chính sách.

## Error boundary cho từng phần của trang

Bọc những phần dễ lỗi của giao diện để một widget hỏng không kéo sập cả trang:

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

export function Dashboard() {
  return (
    <ErrorBoundary
      fallback={({ resetError }) => (
        <div role="alert">
          <p>Biểu đồ này chưa tải được.</p>
          <button onClick={resetError}>Thử lại</button>
        </div>
      )}
    >
      <RevenueChart />
    </ErrorBoundary>
  );
}

// Hoặc bọc một component một lần:
export const SafeRevenueChart = withErrorBoundary(RevenueChart, {
  fallback: <p>Biểu đồ này chưa tải được.</p>,
});
```

`ErrorBoundary` báo mỗi lỗi render một lần và hiện `fallback`. Hộp thoại xin phản hồi người dùng mà một số SDK có thì không có ở đây: nó sẽ phải tải script từ mạng.

> [!NOTE]
> Router có màn hình lỗi riêng — ví dụ `errorElement` của React Router — bắt lỗi trước mọi boundary phía trên. Gọi `captureException(error)` bên trong màn hình lỗi đó, hoặc bọc nó bằng `ErrorBoundary`.

## Báo những lỗi bạn đã xử lý

Mọi thứ của `@belvador/browser` cũng được export ở đây:

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

setUser({ id: user.id });

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

## Kiểm tra

Thêm tạm một nút, bấm thử rồi mở project trong 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)}>Gửi lỗi thử</button>;
}
```

Issue hiện component stack trong mục **Ngữ cảnh**. Nhớ xoá nút sau khi thử.

## Giới hạn cần biết

- Hàm xử lý sự kiện (`onClick`, …) và code bất đồng bộ không nằm trong phạm vi của error boundary — đây là quy tắc của React. Lỗi ở đó mà không ai bắt vẫn tới Belvador qua bộ bắt lỗi toàn cục; lỗi bạn tự bắt thì cần `captureException`.
- Các framework render phía máy chủ khác ngoài TanStack Start không nằm trong phạm vi cài đặt của gói này.
