|
5 | 5 | - [x] FlareErrorBoundary supports `fallback` property |
6 | 6 | - [x] FlareErrorBoundary supports fallback with a reset method for resetting the Error Boundary |
7 | 7 | - [x] FlareErrorBoundary fallback passes `componentStack` |
8 | | -- [x] FlareErrorBoundary supports `onError` callback |
9 | | -- [x] FlareErrorBoundary supports `beforeCapture` callback |
| 8 | +- [x] FlareErrorBoundary supports `beforeEvaluate` callback |
| 9 | +- [x] FlareErrorBoundary supports `beforeSubmit` callback |
| 10 | +- [x] FlareErrorBoundary supports `afterSubmit` callback |
10 | 11 | - [x] FlareErrorBoundary supports `onReset` property |
11 | 12 | - [x] FlareErrorBoundary `onReset` passes previous error |
12 | 13 | - [x] FlareErrorBoundary supports `resetKeys` property |
13 | | -- [x] Add `flareReactErrorHandler` |
| 14 | +- [x] Add `flareReactErrorHandler` with `beforeEvaluate`, `beforeSubmit`, and `afterSubmit` callbacks |
14 | 15 | - [x] Structured component stack parsing with sourcemap-ready frames |
15 | 16 |
|
16 | 17 | ## FlareErrorBoundary: `fallback` property |
@@ -47,37 +48,64 @@ The `fallback` prop accepts either a static `ReactNode` or a render function. Th |
47 | 48 | </FlareErrorBoundary> |
48 | 49 | ``` |
49 | 50 |
|
50 | | -## FlareErrorBoundary: `onError` callback |
| 51 | +## FlareErrorBoundary: `beforeEvaluate` callback |
51 | 52 |
|
52 | 53 | ### Why |
53 | 54 |
|
54 | | -Developers need a hook to perform side effects when an error is caught -- logging to a secondary service, |
55 | | -showing a toast, updating app state, etc. This fires *after* the error has been reported to Flare. |
| 55 | +Fires *before* the component stack context is built, giving developers a chance to attach custom context, tags, or |
| 56 | +user information to the Flare report. This is the pragmatic answer to "how do we capture component props/state" -- let |
| 57 | +the developer decide what to include rather than trying to automatically serialize React internals. |
56 | 58 |
|
57 | 59 | ```tsx |
58 | 60 | <FlareErrorBoundary |
59 | | - onError={({ error, errorInfo }) => { |
60 | | - console.error('Caught by FlareErrorBoundary:', error); |
61 | | - console.error('Component stack:', errorInfo.componentStack); |
| 61 | + beforeEvaluate={({ error, errorInfo }) => { |
| 62 | + flare.addContext('user', { id: currentUser.id }); |
| 63 | + flare.addContext('feature-flags', getActiveFlags()); |
62 | 64 | }} |
63 | 65 | > |
64 | 66 | <App /> |
65 | 67 | </FlareErrorBoundary> |
66 | 68 | ``` |
67 | 69 |
|
68 | | -## FlareErrorBoundary: `beforeCapture` callback |
| 70 | +## FlareErrorBoundary: `beforeSubmit` callback |
69 | 71 |
|
70 | 72 | ### Why |
71 | 73 |
|
72 | | -Fires *before* the error is reported to Flare, giving developers a chance to attach custom context, tags, or |
73 | | -user information to the Flare report. This is the pragmatic answer to "how do we capture component props/state" -- let |
74 | | -the developer decide what to include rather than trying to automatically serialize React internals. |
| 74 | +Fires after the component stack context is built but *before* the error is reported to Flare. The callback receives |
| 75 | +the `context` and must return a (possibly modified) context object. Use this to filter or enrich the report context. |
75 | 76 |
|
76 | 77 | ```tsx |
77 | 78 | <FlareErrorBoundary |
78 | | - beforeCapture={({ error, errorInfo }) => { |
79 | | - flare.addContext('user', { id: currentUser.id }); |
80 | | - flare.addContext('feature-flags', getActiveFlags()); |
| 79 | + beforeSubmit={({ error, errorInfo, context }) => { |
| 80 | + return { |
| 81 | + ...context, |
| 82 | + react: { |
| 83 | + ...context.react, |
| 84 | + componentStack: context.react.componentStack.filter( |
| 85 | + (line) => !line.includes('ThirdPartyWrapper'), |
| 86 | + ), |
| 87 | + }, |
| 88 | + }; |
| 89 | + }} |
| 90 | +> |
| 91 | + <App /> |
| 92 | +</FlareErrorBoundary> |
| 93 | +``` |
| 94 | + |
| 95 | +## FlareErrorBoundary: `afterSubmit` callback |
| 96 | + |
| 97 | +### Why |
| 98 | + |
| 99 | +Developers need a hook to perform side effects when an error is caught -- logging to a secondary service, |
| 100 | +showing a toast, updating app state, etc. This fires *after* the error has been reported to Flare. The callback |
| 101 | +receives the final context that was submitted. |
| 102 | + |
| 103 | +```tsx |
| 104 | +<FlareErrorBoundary |
| 105 | + afterSubmit={({ error, errorInfo, context }) => { |
| 106 | + console.error('Caught by FlareErrorBoundary:', error); |
| 107 | + console.error('Component stack:', errorInfo.componentStack); |
| 108 | + console.error('Reported context:', context); |
81 | 109 | }} |
82 | 110 | > |
83 | 111 | <App /> |
@@ -142,22 +170,37 @@ function App() { |
142 | 170 | React 19 introduced `onCaughtError`, `onUncaughtError`, and `onRecoverableError` callbacks on `createRoot`. |
143 | 171 | These are root-level error handlers that catch errors *without* requiring an ErrorBoundary wrapper. |
144 | 172 |
|
145 | | -`flareReactErrorHandler` is a wrapper function that accepts an optional callback. It also handles non-Error values |
146 | | -(strings, objects) by converting them to proper Error instances via `convertToError()`, making it more resilient to |
147 | | -edge cases. |
| 173 | +`flareReactErrorHandler` is a wrapper function that accepts an optional options object with `beforeEvaluate`, |
| 174 | +`beforeSubmit`, and `afterSubmit` callbacks -- the same callback pattern used by `FlareErrorBoundary`. It also handles |
| 175 | +non-Error values (strings, objects) by converting them to proper Error instances via `convertToError()`, making it more |
| 176 | +resilient to edge cases. |
| 177 | + |
| 178 | +### Callback lifecycle |
| 179 | + |
| 180 | +1. **`beforeEvaluate`** -- called after the error is converted to an `Error`, before building the component stack context. Use this to attach custom context to Flare (e.g. user info, feature flags). |
| 181 | +2. **`beforeSubmit`** -- called with the built context, must return a (possibly modified) context object. Use this to filter or enrich the report context before it is sent. |
| 182 | +3. **`flare.report()`** -- the error is reported to Flare. |
| 183 | +4. **`afterSubmit`** -- called after the report is sent. Use this for side effects like logging or showing a toast. |
148 | 184 |
|
149 | 185 | ```tsx |
150 | 186 | import { flareReactErrorHandler } from '@flareapp/react'; |
151 | 187 |
|
152 | 188 | const root = createRoot(document.getElementById('root')!, { |
153 | 189 | // Errors caught by an Error Boundary |
154 | | - onCaughtError: flareReactErrorHandler((error, errorInfo) => { |
155 | | - console.warn('Caught error:', error); |
| 190 | + onCaughtError: flareReactErrorHandler({ |
| 191 | + afterSubmit: ({ error, errorInfo }) => { |
| 192 | + console.warn('Caught error:', error); |
| 193 | + }, |
156 | 194 | }), |
157 | 195 |
|
158 | 196 | // Errors NOT caught by any Error Boundary |
159 | | - onUncaughtError: flareReactErrorHandler((error, errorInfo) => { |
160 | | - console.error('Uncaught error:', error); |
| 197 | + onUncaughtError: flareReactErrorHandler({ |
| 198 | + beforeEvaluate: ({ error }) => { |
| 199 | + flare.addContext('user', { id: currentUser.id }); |
| 200 | + }, |
| 201 | + afterSubmit: ({ error }) => { |
| 202 | + console.error('Uncaught error:', error); |
| 203 | + }, |
161 | 204 | }), |
162 | 205 |
|
163 | 206 | // Errors React recovers from automatically (e.g. hydration mismatches) |
|
0 commit comments