Skip to content

Commit 0262b1b

Browse files
committed
Update error boundary docs to address differences between framework/data modes
1 parent 89c55a8 commit 0262b1b

File tree

1 file changed

+115
-3
lines changed

1 file changed

+115
-3
lines changed

docs/how-to/error-boundary.md

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ All applications should at a minimum export a root error boundary. This one hand
2121
- Instances of errors with a stack trace
2222
- Randomly thrown values
2323

24-
```tsx filename=root.tsx
24+
### Framework Mode
25+
26+
[modes: framework]
27+
28+
In [Framework Mode][picking-a-mode], errors are passed to the route-level error boundary as a prop (see [`Route.ErrorBoundaryProps`][type-safety]), so you don't need to use a hook to grab it:
29+
30+
```tsx filename=root.tsx lines=[1,3-5]
2531
import { Route } from "./+types/root";
2632

2733
export function ErrorBoundary({
@@ -51,8 +57,57 @@ export function ErrorBoundary({
5157
}
5258
```
5359

60+
### Data Mode
61+
62+
[modes: data]
63+
64+
In [Data Mode][picking-a-mode], the `ErrorBoundary` doesn't receive props, so you can access it via `useRouteError`:
65+
66+
```tsx lines=[1,6,16]
67+
import { useRouteError } from "react-router";
68+
69+
let router = createBrowserRouter([
70+
{
71+
path: "/",
72+
ErrorBoundary: RootErrorBoundary,
73+
Component: Root,
74+
},
75+
]);
76+
77+
function Root() {
78+
/* ... */
79+
}
80+
81+
function RootErrorBoundary() {
82+
let error = useRouteError();
83+
if (isRouteErrorResponse(error)) {
84+
return (
85+
<>
86+
<h1>
87+
{error.status} {error.statusText}
88+
</h1>
89+
<p>{error.data}</p>
90+
</>
91+
);
92+
} else if (error instanceof Error) {
93+
return (
94+
<div>
95+
<h1>Error</h1>
96+
<p>{error.message}</p>
97+
<p>The stack trace is:</p>
98+
<pre>{error.stack}</pre>
99+
</div>
100+
);
101+
} else {
102+
return <h1>Unknown Error</h1>;
103+
}
104+
}
105+
```
106+
54107
## 2. Write a bug
55108

109+
[modes: framework,data]
110+
56111
It's not recommended to intentionally throw errors to force the error boundary to render as a means of control flow. Error Boundaries are primarily for catching unintentional errors in your code.
57112

58113
```tsx
@@ -67,6 +122,8 @@ This is not just for loaders, but for all route module APIs: loaders, actions, c
67122

68123
## 3. Throw data in loaders/actions
69124

125+
[modes: framework,data]
126+
70127
There are exceptions to the rule in #2, especially 404s. You can intentionally `throw data()` (with a proper status code) to the closest error boundary when your loader can't find what it needs to render the page. Throw a 404 and move on.
71128

72129
```tsx
@@ -85,7 +142,13 @@ This will render the `isRouteErrorResponse` branch of the UI from step 1.
85142

86143
## 4. Nested error boundaries
87144

88-
When an error is thrown, the "closest error boundary" will be rendered. Consider these nested routes:
145+
When an error is thrown, the "closest error boundary" will be rendered.
146+
147+
### Framework Mode
148+
149+
[modes: framework]
150+
151+
Consider these nested routes:
89152

90153
```tsx filename="routes.ts"
91154
// ✅ has error boundary
@@ -110,10 +173,59 @@ The following table shows which error boundary will render given the origin of t
110173
| invoice-page.tsx | invoice-page.tsx |
111174
| payments.tsx | invoice-page.tsx |
112175

176+
### Data Mode
177+
178+
[modes: data]
179+
180+
In Data Mode, the equivalent route tree might look like:
181+
182+
```tsx
183+
let router = createBrowserRouter([
184+
{
185+
path: "/app",
186+
Component: App,
187+
ErrorBoundary: AppErrorBoundary, // ✅ has error boundary
188+
children: [
189+
{
190+
path: "invoices",
191+
Component: Invoices, // ❌ no error boundary
192+
children: [
193+
{
194+
path: ":id",
195+
Component: Invoice,
196+
ErrorBoundary: InvoiceErrorBoundary, // ✅ has error boundary
197+
children: [
198+
{
199+
path: "payments",
200+
Component: Payments, // ❌ no error boundary
201+
},
202+
],
203+
},
204+
],
205+
},
206+
],
207+
},
208+
]);
209+
```
210+
211+
The following table shows which error boundary will render given the origin of the error:
212+
213+
| error origin | rendered boundary |
214+
| ------------ | ---------------------- |
215+
| `App` | `AppErrorBoundary` |
216+
| `Invoices` | `AppErrorBoundary` |
217+
| `Invoice` | `InvoiceErrorBoundary` |
218+
| `Payments` | `InvoiceErrorBoundary` |
219+
113220
## Error Sanitization
114221

115-
In production mode, any errors that happen on the server are automatically sanitized before being sent to the browser to prevent leaking any sensitive server information (like stack traces).
222+
[modes: framework]
223+
224+
In Framework Mode when building for production, any errors that happen on the server are automatically sanitized before being sent to the browser to prevent leaking any sensitive server information (like stack traces).
116225

117226
This means that a thrown `Error` will have a generic message and no stack trace in production in the browser. The original error is untouched on the server.
118227

119228
Also note that data sent with `throw data(yourData)` is not sanitized as the data there is intended to be rendered.
229+
230+
[picking-a-mode]: ../start/modes
231+
[type-safety]: ../explanation/type-safety

0 commit comments

Comments
 (0)