Skip to content

Commit d32e1b2

Browse files
committed
Troubleshooting empty stacks sandbox
1 parent 66562e8 commit d32e1b2

File tree

1 file changed

+247
-8
lines changed

1 file changed

+247
-8
lines changed

src/content/reference/react/captureOwnerStack.md

Lines changed: 247 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@ function Component() {
4747

4848
`captureOwnerStack` returns `string | null`.
4949

50-
If no Owner Stack is available (outside of render, Effects, Events and React error handlers), it returns an empty string (see [Troubleshooting: The Owner Stack is empty](#the-owner-stack-is-empty-the-owner-stack-is-empty)). Outside of development builds, `null` is returned (see [Troubleshooting: The Owner Stack is `null`](#the-owner-stack-is-null-the-owner-stack-is-null)).
50+
Owner Stacks are available in
51+
- Component render
52+
- Effects (e.g. `useEffect`)
53+
- React's event handlers (e.g. `<button onClick={...} />`)
54+
- React error handlers ([React Root options](/reference/react-dom/client/createRoot#parameters) `onCaughtError`, `onRecoverableError`, and `onUncaughtError`)
55+
56+
If no Owner Stack is available, an empty string is returned (see [Troubleshooting: The Owner Stack is empty](#the-owner-stack-is-empty-the-owner-stack-is-empty)). Outside of development builds, `null` is returned (see [Troubleshooting: The Owner Stack is `null`](#the-owner-stack-is-null-the-owner-stack-is-null)).
5157

5258
#### Caveats {/*caveats*/}
5359

@@ -109,6 +115,7 @@ createRoot(document.createElement('div'), {
109115
// highlight that browsers will apply sourcemaps to the logged stacks.
110116
// Note that sourcemapping is only applied in the real browser console not
111117
// in the fake one displayed on this page.
118+
// Press "fork" to be able to view the sourcemapped stack in a real console.
112119
console.log(errorInfo.componentStack);
113120
console.log(captureOwnerStack());
114121
},
@@ -179,14 +186,205 @@ Neither `Navigation` nor `legend` are in the stack at all since it's only a sibl
179186

180187
## Usage {/*usage*/}
181188

182-
### Improve error reporting {/*improve-error-reporting*/}
189+
### Enhance a custom error overlay {/*enhance-a-custom-error-overlay*/}
190+
191+
```js [[1, 5, "console.error"], [4, 7, "captureOwnerStack"]]
192+
import { captureOwnerStack } from "react";
193+
import { instrumentedConsoleError } from "./errorOverlay";
194+
195+
const originalConsoleError = console.error;
196+
console.error = function patchedConsoleError(...args) {
197+
originalConsoleError.apply(console, args);
198+
const ownerStack = captureOwnerStack();
199+
onConsoleError({
200+
// Keep in mind that in a real application, console.error can be
201+
// called with multiple arguments which you should account for.
202+
consoleMessage: args[0],
203+
ownerStack,
204+
});
205+
};
206+
```
207+
208+
If you intercept <CodeStep step={1}>`console.error`</CodeStep> calls to highlight them in an error overlay, you can call <CodeStep step={2}>`captureOwnerStack`</CodeStep> to include the Owner Stack.
209+
210+
<Sandpack>
211+
212+
```css src/styles.css
213+
* {
214+
box-sizing: border-box;
215+
}
216+
217+
body {
218+
font-family: sans-serif;
219+
margin: 20px;
220+
padding: 0;
221+
}
222+
223+
h1 {
224+
margin-top: 0;
225+
font-size: 22px;
226+
}
227+
228+
h2 {
229+
margin-top: 0;
230+
font-size: 20px;
231+
}
232+
233+
code {
234+
font-size: 1.2em;
235+
}
236+
237+
ul {
238+
padding-inline-start: 20px;
239+
}
240+
241+
label, button { display: block; margin-bottom: 20px; }
242+
html, body { min-height: 300px; }
243+
244+
#error-dialog {
245+
position: absolute;
246+
top: 0;
247+
right: 0;
248+
bottom: 0;
249+
left: 0;
250+
background-color: white;
251+
padding: 15px;
252+
opacity: 0.9;
253+
text-wrap: wrap;
254+
overflow: scroll;
255+
}
256+
257+
.text-red {
258+
color: red;
259+
}
260+
261+
.-mb-20 {
262+
margin-bottom: -20px;
263+
}
264+
265+
.mb-0 {
266+
margin-bottom: 0;
267+
}
268+
269+
.mb-10 {
270+
margin-bottom: 10px;
271+
}
272+
273+
pre {
274+
text-wrap: wrap;
275+
}
276+
277+
pre.nowrap {
278+
text-wrap: nowrap;
279+
}
280+
281+
.hidden {
282+
display: none;
283+
}
284+
```
285+
286+
```html public/index.html hidden
287+
<!DOCTYPE html>
288+
<html>
289+
<head>
290+
<title>My app</title>
291+
</head>
292+
<body>
293+
<!--
294+
Error dialog in raw HTML
295+
since an error in the React app may crash.
296+
-->
297+
<div id="error-dialog" class="hidden">
298+
<h1 id="error-title" class="text-red">Error</h1>
299+
<p>
300+
<pre id="error-body"></pre>
301+
</p>
302+
<h2 class="-mb-20">Owner stack:</h4>
303+
<pre id="error-owner-stack" class="nowrap"></pre>
304+
<button
305+
id="error-close"
306+
class="mb-10"
307+
onclick="document.getElementById('error-dialog').classList.add('hidden')"
308+
>
309+
Close
310+
</button>
311+
</div>
312+
<!-- This is the DOM node -->
313+
<div id="root"></div>
314+
</body>
315+
</html>
316+
317+
```
318+
319+
```js src/errorOverlay.js
320+
321+
export function onConsoleError({ consoleMessage, ownerStack }) {
322+
const errorDialog = document.getElementById("error-dialog");
323+
const errorBody = document.getElementById("error-body");
324+
const errorOwnerStack = document.getElementById("error-owner-stack");
325+
const errorStack = document.getElementById("error-stack");
326+
327+
// Display console.error() message
328+
errorBody.innerText = consoleMessage;
329+
330+
// Display owner stack
331+
errorOwnerStack.innerText = ownerStack;
332+
333+
// Show the dialog
334+
errorDialog.classList.remove("hidden");
335+
}
336+
```
337+
338+
```js src/index.js active
339+
import { captureOwnerStack } from "react";
340+
import { createRoot } from "react-dom/client";
341+
import App from './App';
342+
import { onConsoleError } from "./errorOverlay";
343+
import './styles.css';
344+
345+
const originalConsoleError = console.error;
346+
console.error = function patchedConsoleError(...args) {
347+
originalConsoleError.apply(console, args);
348+
const ownerStack = captureOwnerStack();
349+
onConsoleError({
350+
// Keep in mind that in a real application, console.error can be
351+
// called with multiple arguments which you should account for.
352+
consoleMessage: args[0],
353+
ownerStack,
354+
});
355+
};
356+
357+
const container = document.getElementById("root");
358+
createRoot(container).render(<App />);
359+
```
360+
361+
```json package.json hidden
362+
{
363+
"dependencies": {
364+
"react": "experimental",
365+
"react-dom": "experimental",
366+
"react-scripts": "latest"
367+
},
368+
"scripts": {
369+
"start": "react-scripts start",
370+
"build": "react-scripts build",
371+
"test": "react-scripts test --env=jsdom",
372+
"eject": "react-scripts eject"
373+
}
374+
}
375+
```
376+
377+
```js src/App.js
378+
function Component() {
379+
return <button onClick={() => console.error('Some console error')}>Trigger console.error()</button>;
380+
}
183381

184-
Check out the following example to see how to use `captureOwnerStack` to improve error reporting:
382+
export default function App() {
383+
return <Component />;
384+
}
385+
```
185386

186-
- [createRoot usage: Show a dialog for uncaught errors
187-
](/reference/react-dom/client/createRoot#show-a-dialog-for-uncaught-errors)
188-
- [createRoot usage: Displaying Error Boundary errors](/reference/react-dom/client/createRoot#show-a-dialog-for-uncaught-errors)
189-
- [createRoot usage: Displaying a dialog for recoverable errors](/reference/react-dom/client/createRoot#displaying-a-dialog-for-recoverable-errors)
387+
</Sandpack>
190388

191389
### Expanding error stacks {/*expanding-error-stacks*/}
192390

@@ -295,4 +493,45 @@ const root = createRoot(document.getElementById('root'), {
295493

296494
### The Owner Stack is empty {/*the-owner-stack-is-empty*/}
297495

298-
The call of `captureOwnerStack` happened outside of a React controlled function e.g. in a `setTimeout` callback. During render, Effects, Events, and React error handlers (e.g. `hydrateRoot#options.onCaughtError`) Owner Stacks should be available.
496+
The call of `captureOwnerStack` happened outside of a React controlled function e.g. in a `setTimeout` callback, after a fetch or in a custom DOM event handler. During render, Effects, React event handlers, and React error handlers (e.g. `hydrateRoot#options.onCaughtError`) Owner Stacks should be available.
497+
498+
In the example below, clicking the button will log an empty Owner Stack because `captureOwnerStack` was called during a custom DOM event handler. The Owner Stack must be captured earlier e.g. by moving the call of `captureOwnerStack` into the Effect body.
499+
<Sandpack>
500+
501+
```js
502+
import {captureOwnerStack, useEffect} from 'react';
503+
504+
export default function App() {
505+
useEffect(() => {
506+
function handleEvent() {
507+
console.log('Owner Stack: ', captureOwnerStack());
508+
}
509+
510+
document.addEventListener('click', handleEvent);
511+
512+
return () => {
513+
document.removeEventListener('click', handleEvent);
514+
}
515+
})
516+
517+
return <button>Click me to see that Owner Stacks are not available in custom DOM event handlers</button>;
518+
}
519+
```
520+
521+
```json package.json hidden
522+
{
523+
"dependencies": {
524+
"react": "experimental",
525+
"react-dom": "experimental",
526+
"react-scripts": "latest"
527+
},
528+
"scripts": {
529+
"start": "react-scripts start",
530+
"build": "react-scripts build",
531+
"test": "react-scripts test --env=jsdom",
532+
"eject": "react-scripts eject"
533+
}
534+
}
535+
```
536+
537+
</Sandpack>

0 commit comments

Comments
 (0)