From c1948dc6e7e26c761c1b5478b7137521966b0ef3 Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Mon, 13 Jan 2025 14:32:17 +0100
Subject: [PATCH 01/20] Reference docs for `captureOwnerStack`
---
.../reference/react/captureOwnerStack.md | 180 ++++++++++++++++++
src/sidebarReference.json | 8 +-
2 files changed, 186 insertions(+), 2 deletions(-)
create mode 100644 src/content/reference/react/captureOwnerStack.md
diff --git a/src/content/reference/react/captureOwnerStack.md b/src/content/reference/react/captureOwnerStack.md
new file mode 100644
index 00000000000..96b8c25d928
--- /dev/null
+++ b/src/content/reference/react/captureOwnerStack.md
@@ -0,0 +1,180 @@
+---
+title: captureOwnerStack
+---
+
+
+
+**This API is experimental and is not available in a stable version of React yet.**
+
+You can try it by upgrading React packages to the most recent experimental version:
+
+- `react@experimental`
+
+Experimental versions of React may contain bugs. Don't use them in production.
+
+
+
+
+
+`captureOwnerStack` reads the current **owner** Component stack and returns it as a string.
+If no owner stack is available, it returns an empty string.
+
+```js
+captureOwnerStack();
+```
+
+
+
+
+
+---
+
+## Reference {/*reference*/}
+
+### `captureOwnerStack()` {/*captureownerstack*/}
+
+Call `captureOwnerStack` to get the current owner Component stack.
+
+```js
+import * as React from 'react';
+
+function Component() {
+ if (process.env.NODE_ENV !== 'production') {
+ const ownerStack = React.captureOwnerStack();
+ console.log(ownerStack);
+ }
+}
+```
+
+#### Parameters {/*parameters*/}
+
+`captureOwnerStack` does not take any parameters.
+
+#### Returns {/*returns*/}
+
+`captureOwnerStack` returns `string`.
+
+#### Caveats {/*caveats*/}
+
+`captureOwnerStack` is only available in development builds of React.
+In production builds, the `captureOwnerStack` export does not exist.
+
+Only call `captureOwnerStack` in development environments:
+
+```tsx
+// Use a namespace import to avoid errors in production when using ES modules.
+// Non-existing exports throw a `SyntaxError` in ES modules.
+import * as React from 'react';
+
+let ownerStack = '';
+if (process.env.NODE_ENV !== 'prodction') {
+ ownerStack = React.captureOwnerStack();
+}
+```
+
+## Owner Component stacks vs parent Component stacks {/*owner-component-stacks-vs-parent-component-stacks*/}
+
+The owner Component stack is different from the **parent** Component stack available error handlers like [`errorInfo.componentStack` in `onUncaughtError`](http://localhost:3000/reference/react-dom/client/hydrateRoot#show-a-dialog-for-uncaught-errors).
+
+For example, consider the following code:
+
+```tsx
+import * as React from 'react';
+import * as ReactDOMClient from 'react-dom/client';
+
+function SubComponent({disabled}) {
+ if (disabled) {
+ throw new Error('disabled');
+ }
+}
+
+function Component({label}) {
+ return (
+
+ );
+}
+
+function Navigation() {
+ return null;
+}
+
+function App({children}) {
+ return (
+
+
+
+ {children}
+
+
+ );
+}
+
+createRoot(document.createElement('div')).render(
+
+
+
+);
+```
+
+`SubComponent` would throw an error.
+The **parent** component stack of that error would be
+
+```
+at SubComponent
+at fieldset
+at Component
+at main
+at React.Suspense
+at App
+```
+
+However, the owner stack would only read
+
+```
+at SubComponent
+at Component
+```
+
+Neither `App` nor the DOM components (e.g. `fieldset`) are considered owners in this stack since they didn't contribute to "creating" the node containing `SubComponent`. `App` and DOM components only forwarded the node. `App` just rendered the `children` node as opposed to `Component` which created a node containing `SubComponent` via ``.
+
+Neither `Navigation` nor `legend` are in the stack at all since it's only a sibling to a node containing ``.
+
+### When to use which {/*when-to-use-which*/}
+
+The parent stack is useful for contextual information e.g. [`React.Suspense`](/reference/react/Suspense), [React Context](https://react.dev/reference/react/createContext), or [`
+ <>
This error shows the error dialog:
-
+
+ >
+ );
+}
+
+export default function App() {
+ return (
+
+
);
}
```
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "experimental",
+ "react-dom": "experimental",
+ "react-scripts": "^5.0.0",
+ "react-error-boundary": "4.0.3"
+ }
+}
+```
+
@@ -579,7 +612,9 @@ export default function App() {
By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option to handle errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary):
-```js [[1, 6, "onCaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack"]]
+```js [[1, 8, "onCaughtError"], [2, 8, "error", 1], [3, 8, "errorInfo"], [4, 12, "componentStack"], [5, 13, "captureOwnerStack()"]]
+// captureOwnerStack is only available in react@experimental.
+import { captureOwnerStack } from 'react';
import { createRoot } from 'react-dom/client';
const root = createRoot(
@@ -589,7 +624,8 @@ const root = createRoot(
console.error(
'Caught error',
error,
- errorInfo.componentStack
+ errorInfo.componentStack,
+ ownerStack: captureOwnerStack()
);
}
}
@@ -602,6 +638,8 @@ The onCaughtError option is a function called with
1. The error that was caught by the boundary.
2. An errorInfo object that contains the componentStack of the error.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+
You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging:
@@ -625,8 +663,10 @@ You can use the `onCaughtError` root option to display error dialogs or filter k
-
This error occurred at:
+
Component stack:
+
Owner stack:
+
Call stack:
@@ -696,12 +736,13 @@ pre.nowrap {
```
```js src/reportError.js hidden
-function reportError({ title, error, componentStack, dismissable }) {
+function reportError({ title, error, componentStack, ownerStack, dismissable }) {
const errorDialog = document.getElementById("error-dialog");
const errorTitle = document.getElementById("error-title");
const errorMessage = document.getElementById("error-message");
const errorBody = document.getElementById("error-body");
const errorComponentStack = document.getElementById("error-component-stack");
+ const errorOwnerStack = document.getElementById("error-owner-stack");
const errorStack = document.getElementById("error-stack");
const errorClose = document.getElementById("error-close");
const errorCause = document.getElementById("error-cause");
@@ -724,6 +765,9 @@ function reportError({ title, error, componentStack, dismissable }) {
// Display component stack
errorComponentStack.innerText = componentStack;
+ // Display owner stack
+ errorOwnerStack.innerText = ownerStack;
+
// Display the call stack
// Since we already displayed the message, strip it, and the first Error: line.
errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1];
@@ -749,20 +793,22 @@ function reportError({ title, error, componentStack, dismissable }) {
errorDialog.classList.remove("hidden");
}
-export function reportCaughtError({error, cause, componentStack}) {
- reportError({ title: "Caught Error", error, componentStack, dismissable: true});
+export function reportCaughtError({error, cause, componentStack, ownerStack}) {
+ reportError({ title: "Caught Error", error, componentStack, ownerStack, dismissable: true});
}
-export function reportUncaughtError({error, cause, componentStack}) {
- reportError({ title: "Uncaught Error", error, componentStack, dismissable: false });
+export function reportUncaughtError({error, cause, componentStack, ownerStack}) {
+ reportError({ title: "Uncaught Error", error, componentStack, ownerStack, dismissable: false });
}
-export function reportRecoverableError({error, cause, componentStack}) {
- reportError({ title: "Recoverable Error", error, componentStack, dismissable: true });
+export function reportRecoverableError({error, cause, componentStack, ownerStack}) {
+ reportError({ title: "Recoverable Error", error, componentStack, ownerStack, dismissable: true });
}
```
```js src/index.js active
+// captureOwnerStack is only available in react@experimental.
+import {captureOwnerStack} from 'react';
import { createRoot } from "react-dom/client";
import App from "./App.js";
import {reportCaughtError} from "./reportError";
@@ -775,6 +821,7 @@ const root = createRoot(container, {
reportCaughtError({
error,
componentStack: errorInfo.componentStack,
+ ownerStack: captureOwnerStack()
});
}
}
@@ -842,12 +889,11 @@ function Throw({error}) {
```json package.json hidden
{
"dependencies": {
- "react": "19.0.0-rc-3edc000d-20240926",
- "react-dom": "19.0.0-rc-3edc000d-20240926",
+ "react": "experimental",
+ "react-dom": "experimental",
"react-scripts": "^5.0.0",
"react-error-boundary": "4.0.3"
- },
- "main": "/index.js"
+ }
}
```
@@ -857,7 +903,9 @@ function Throw({error}) {
React may automatically render a component a second time to attempt to recover from an error thrown in render. If successful, React will log a recoverable error to the console to notify the developer. To override this behavior, you can provide the optional `onRecoverableError` root option:
-```js [[1, 6, "onRecoverableError"], [2, 6, "error", 1], [3, 10, "error.cause"], [4, 6, "errorInfo"], [5, 11, "componentStack"]]
+```js [[1, 8, "onRecoverableError"], [2, 8, "error", 1], [3, 12, "error.cause"], [4, 8, "errorInfo"], [5, 13, "componentStack"], [6, 14, "captureOwnerStack()"]]
+// captureOwnerStack is only available in react@experimental.
+import { captureOwnerStack } from 'react';
import { createRoot } from 'react-dom/client';
const root = createRoot(
@@ -869,6 +917,7 @@ const root = createRoot(
error,
error.cause,
errorInfo.componentStack,
+ captureOwnerStack(),
);
}
}
@@ -881,6 +930,8 @@ The onRecoverableError option is a function called
1. The error that React throws. Some errors may include the original cause as error.cause.
2. An errorInfo object that contains the componentStack of the error.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+
You can use the `onRecoverableError` root option to display error dialogs:
@@ -904,8 +955,10 @@ You can use the `onRecoverableError` root option to display error dialogs:
-
This error occurred at:
+
Component stack:
+
Owner stack:
+
Call stack:
@@ -975,12 +1028,13 @@ pre.nowrap {
```
```js src/reportError.js hidden
-function reportError({ title, error, componentStack, dismissable }) {
+function reportError({ title, error, componentStack, ownerStack, dismissable }) {
const errorDialog = document.getElementById("error-dialog");
const errorTitle = document.getElementById("error-title");
const errorMessage = document.getElementById("error-message");
const errorBody = document.getElementById("error-body");
const errorComponentStack = document.getElementById("error-component-stack");
+ const errorOwnerStack = document.getElementById("error-owner-stack");
const errorStack = document.getElementById("error-stack");
const errorClose = document.getElementById("error-close");
const errorCause = document.getElementById("error-cause");
@@ -1003,6 +1057,9 @@ function reportError({ title, error, componentStack, dismissable }) {
// Display component stack
errorComponentStack.innerText = componentStack;
+ // Display owner stack
+ errorOwnerStack.innerText = ownerStack;
+
// Display the call stack
// Since we already displayed the message, strip it, and the first Error: line.
errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1];
@@ -1028,20 +1085,22 @@ function reportError({ title, error, componentStack, dismissable }) {
errorDialog.classList.remove("hidden");
}
-export function reportCaughtError({error, cause, componentStack}) {
- reportError({ title: "Caught Error", error, componentStack, dismissable: true});
+export function reportCaughtError({error, cause, componentStack, ownerStack}) {
+ reportError({ title: "Caught Error", error, componentStack, ownerStack, dismissable: true});
}
-export function reportUncaughtError({error, cause, componentStack}) {
- reportError({ title: "Uncaught Error", error, componentStack, dismissable: false });
+export function reportUncaughtError({error, cause, componentStack, ownerStack}) {
+ reportError({ title: "Uncaught Error", error, componentStack, ownerStack, dismissable: false });
}
-export function reportRecoverableError({error, cause, componentStack}) {
- reportError({ title: "Recoverable Error", error, componentStack, dismissable: true });
+export function reportRecoverableError({error, cause, componentStack, ownerStack}) {
+ reportError({ title: "Recoverable Error", error, componentStack, ownerStack, dismissable: true });
}
```
```js src/index.js active
+// captureOwnerStack is only available in react@experimental.
+import {captureOwnerStack} from 'react'
import { createRoot } from "react-dom/client";
import App from "./App.js";
import {reportRecoverableError} from "./reportError";
@@ -1054,6 +1113,7 @@ const root = createRoot(container, {
error,
cause: error.cause,
componentStack: errorInfo.componentStack,
+ ownerStack: captureOwnerStack(),
});
}
});
@@ -1100,8 +1160,8 @@ function Throw({error}) {
```json package.json hidden
{
"dependencies": {
- "react": "19.0.0-rc-3edc000d-20240926",
- "react-dom": "19.0.0-rc-3edc000d-20240926",
+ "react": "experimental",
+ "react-dom": "experimental",
"react-scripts": "^5.0.0",
"react-error-boundary": "4.0.3"
},
diff --git a/src/content/reference/react-dom/client/hydrateRoot.md b/src/content/reference/react-dom/client/hydrateRoot.md
index b1eeca30ce6..914337e371b 100644
--- a/src/content/reference/react-dom/client/hydrateRoot.md
+++ b/src/content/reference/react-dom/client/hydrateRoot.md
@@ -378,10 +378,12 @@ It is uncommon to call [`root.render`](#root-render) on a hydrated root. Usually
By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option:
-```js [[1, 7, "onUncaughtError"], [2, 7, "error", 1], [3, 7, "errorInfo"], [4, 11, "componentStack"]]
+```js [[1, 9, "onUncaughtError"], [2, 9, "error", 1], [3, 9, "errorInfo"], [4, 13, "componentStack"], [5, 14, "captureOwnerStack()"]]
+// captureOwnerStack is only available in react@experimental.
+import { captureOwnerStack } from 'react';
import { hydrateRoot } from 'react-dom/client';
-const root = hydrateRoot(
+hydrateRoot(
document.getElementById('root'),
,
{
@@ -389,12 +391,12 @@ const root = hydrateRoot(
console.error(
'Uncaught error',
error,
- errorInfo.componentStack
+ errorInfo.componentStack,
+ captureOwnerStack()
);
}
}
);
-root.render();
```
The onUncaughtError option is a function called with two arguments:
@@ -402,6 +404,8 @@ The onUncaughtError option is a function called wi
1. The error that was thrown.
2. An errorInfo object that contains the componentStack of the error.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+
You can use the `onUncaughtError` root option to display error dialogs:
@@ -425,8 +429,10 @@ You can use the `onUncaughtError` root option to display error dialogs:
+ <>
This error shows the error dialog:
-
+
+ >
+ );
+}
+
+export default function App() {
+ return (
+
+
);
}
```
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "experimental",
+ "react-dom": "experimental",
+ "react-scripts": "^5.0.0",
+ "react-error-boundary": "4.0.3"
+ }
+}
+```
+
@@ -613,10 +645,12 @@ export default function App() {
By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option for errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary):
-```js [[1, 7, "onCaughtError"], [2, 7, "error", 1], [3, 7, "errorInfo"], [4, 11, "componentStack"]]
+```js [[1, 9, "onCaughtError"], [2, 9, "error", 1], [3, 9, "errorInfo"], [4, 13, "componentStack"], [5, 14, "captureOwnerStack()"]]
+// captureOwnerStack is only available in react@experimental.
+import { captureOwnerStack } from 'react';
import { hydrateRoot } from 'react-dom/client';
-const root = hydrateRoot(
+hydrateRoot(
document.getElementById('root'),
,
{
@@ -624,12 +658,12 @@ const root = hydrateRoot(
console.error(
'Caught error',
error,
- errorInfo.componentStack
+ errorInfo.componentStack,
+ ownerStack: captureOwnerStack()
);
}
}
);
-root.render();
```
The onCaughtError option is a function called with two arguments:
@@ -637,6 +671,8 @@ The onCaughtError option is a function called with
1. The error that was caught by the boundary.
2. An errorInfo object that contains the componentStack of the error.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+
You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging:
@@ -660,8 +696,10 @@ You can use the `onCaughtError` root option to display error dialogs or filter k
-
This error occurred at:
+
Component stack:
+
Owner stack:
+
Call stack:
@@ -734,12 +772,13 @@ pre.nowrap {
```
```js src/reportError.js hidden
-function reportError({ title, error, componentStack, dismissable }) {
+function reportError({ title, error, componentStack, ownerStack, dismissable }) {
const errorDialog = document.getElementById("error-dialog");
const errorTitle = document.getElementById("error-title");
const errorMessage = document.getElementById("error-message");
const errorBody = document.getElementById("error-body");
const errorComponentStack = document.getElementById("error-component-stack");
+ const errorOwnerStack = document.getElementById("error-owner-stack");
const errorStack = document.getElementById("error-stack");
const errorClose = document.getElementById("error-close");
const errorCause = document.getElementById("error-cause");
@@ -762,6 +801,9 @@ function reportError({ title, error, componentStack, dismissable }) {
// Display component stack
errorComponentStack.innerText = componentStack;
+ // Display owner stack
+ errorOwnerStack.innerText = ownerStack;
+
// Display the call stack
// Since we already displayed the message, strip it, and the first Error: line.
errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1];
@@ -787,32 +829,35 @@ function reportError({ title, error, componentStack, dismissable }) {
errorDialog.classList.remove("hidden");
}
-export function reportCaughtError({error, cause, componentStack}) {
- reportError({ title: "Caught Error", error, componentStack, dismissable: true});
+export function reportCaughtError({error, cause, componentStack, ownerStack}) {
+ reportError({ title: "Caught Error", error, componentStack, ownerStack, dismissable: true});
}
export function reportUncaughtError({error, cause, componentStack}) {
- reportError({ title: "Uncaught Error", error, componentStack, dismissable: false });
+ reportError({ title: "Uncaught Error", error, componentStack, ownerStack, dismissable: false });
}
export function reportRecoverableError({error, cause, componentStack}) {
- reportError({ title: "Recoverable Error", error, componentStack, dismissable: true });
+ reportError({ title: "Recoverable Error", error, componentStack, ownerStack, ownerStack, dismissable: true });
}
```
```js src/index.js active
+// captureOwnerStack is only available in react@experimental.
+import {captureOwnerStack} from 'react';
import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import {reportCaughtError} from "./reportError";
import "./styles.css";
const container = document.getElementById("root");
-const root = hydrateRoot(container, , {
+hydrateRoot(container, , {
onCaughtError: (error, errorInfo) => {
if (error.message !== 'Known error') {
reportCaughtError({
error,
- componentStack: errorInfo.componentStack
+ componentStack: errorInfo.componentStack,
+ ownerStack: captureOwnerStack()
});
}
}
@@ -879,12 +924,11 @@ function Throw({error}) {
```json package.json hidden
{
"dependencies": {
- "react": "19.0.0-rc-3edc000d-20240926",
- "react-dom": "19.0.0-rc-3edc000d-20240926",
+ "react": "experimental",
+ "react-dom": "experimental",
"react-scripts": "^5.0.0",
"react-error-boundary": "4.0.3"
- },
- "main": "/index.js"
+ }
}
```
@@ -894,7 +938,9 @@ function Throw({error}) {
When React encounters a hydration mismatch, it will automatically attempt to recover by rendering on the client. By default, React will log hydration mismatch errors to `console.error`. To override this behavior, you can provide the optional `onRecoverableError` root option:
-```js [[1, 7, "onRecoverableError"], [2, 7, "error", 1], [3, 11, "error.cause", 1], [4, 7, "errorInfo"], [5, 12, "componentStack"]]
+```js [[1, 9, "onRecoverableError"], [2, 9, "error", 1], [3, 13, "error.cause", 1], [4, 9, "errorInfo"], [5, 14, "componentStack"], [6, 15, "captureOwnerStack()"]]
+// captureOwnerStack is only available in react@experimental.
+import { captureOwnerStack } from 'react';
import { hydrateRoot } from 'react-dom/client';
const root = hydrateRoot(
@@ -906,7 +952,8 @@ const root = hydrateRoot(
'Caught error',
error,
error.cause,
- errorInfo.componentStack
+ errorInfo.componentStack,
+ captureOwnerStack()
);
}
}
@@ -918,6 +965,8 @@ The onRecoverableError option is a function called
1. The error React throws. Some errors may include the original cause as error.cause.
2. An errorInfo object that contains the componentStack of the error.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+
You can use the `onRecoverableError` root option to display error dialogs for hydration mismatches:
@@ -941,8 +990,10 @@ You can use the `onRecoverableError` root option to display error dialogs for hy
-
This error occurred at:
+
Component stack:
+
Owner stack:
+
Call stack:
@@ -1015,12 +1066,13 @@ pre.nowrap {
```
```js src/reportError.js hidden
-function reportError({ title, error, componentStack, dismissable }) {
+function reportError({ title, error, componentStack, ownerStack, dismissable }) {
const errorDialog = document.getElementById("error-dialog");
const errorTitle = document.getElementById("error-title");
const errorMessage = document.getElementById("error-message");
const errorBody = document.getElementById("error-body");
const errorComponentStack = document.getElementById("error-component-stack");
+ const errorOwnerStack = document.getElementById("error-owner-stack");
const errorStack = document.getElementById("error-stack");
const errorClose = document.getElementById("error-close");
const errorCause = document.getElementById("error-cause");
@@ -1043,6 +1095,9 @@ function reportError({ title, error, componentStack, dismissable }) {
// Display component stack
errorComponentStack.innerText = componentStack;
+ // Display owner stack
+ errorOwnerStack.innerText = ownerStack;
+
// Display the call stack
// Since we already displayed the message, strip it, and the first Error: line.
errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1];
@@ -1068,20 +1123,22 @@ function reportError({ title, error, componentStack, dismissable }) {
errorDialog.classList.remove("hidden");
}
-export function reportCaughtError({error, cause, componentStack}) {
- reportError({ title: "Caught Error", error, componentStack, dismissable: true});
+export function reportCaughtError({error, cause, componentStack, ownerStack}) {
+ reportError({ title: "Caught Error", error, componentStack, ownerStack, dismissable: true});
}
-export function reportUncaughtError({error, cause, componentStack}) {
- reportError({ title: "Uncaught Error", error, componentStack, dismissable: false });
+export function reportUncaughtError({error, cause, componentStack, ownerStack}) {
+ reportError({ title: "Uncaught Error", error, componentStack, ownerStack, dismissable: false });
}
-export function reportRecoverableError({error, cause, componentStack}) {
- reportError({ title: "Recoverable Error", error, componentStack, dismissable: true });
+export function reportRecoverableError({error, cause, componentStack, ownerStack}) {
+ reportError({ title: "Recoverable Error", error, componentStack, ownerStack, dismissable: true });
}
```
```js src/index.js active
+// captureOwnerStack is only available in react@experimental.
+import {captureOwnerStack} from 'react'
import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import {reportRecoverableError} from "./reportError";
@@ -1093,7 +1150,8 @@ const root = hydrateRoot(container, , {
reportRecoverableError({
error,
cause: error.cause,
- componentStack: errorInfo.componentStack
+ componentStack: errorInfo.componentStack,
+ ownerStack: captureOwnerStack()
});
}
});
@@ -1104,16 +1162,6 @@ import { useState } from 'react';
import { ErrorBoundary } from "react-error-boundary";
export default function App() {
- const [error, setError] = useState(null);
-
- function handleUnknown() {
- setError("unknown");
- }
-
- function handleKnown() {
- setError("known");
- }
-
return (
{typeof window !== 'undefined' ? 'Client' : 'Server'}
);
@@ -1141,8 +1189,8 @@ function Throw({error}) {
```json package.json hidden
{
"dependencies": {
- "react": "19.0.0-rc-3edc000d-20240926",
- "react-dom": "19.0.0-rc-3edc000d-20240926",
+ "react": "experimental",
+ "react-dom": "experimental",
"react-scripts": "^5.0.0",
"react-error-boundary": "4.0.3"
},
diff --git a/src/content/reference/react/Component.md b/src/content/reference/react/Component.md
index 99fa17986f0..5e8c8d9679f 100644
--- a/src/content/reference/react/Component.md
+++ b/src/content/reference/react/Component.md
@@ -1273,7 +1273,11 @@ By default, if your application throws an error during rendering, React will rem
To implement an error boundary component, you need to provide [`static getDerivedStateFromError`](#static-getderivedstatefromerror) which lets you update state in response to an error and display an error message to the user. You can also optionally implement [`componentDidCatch`](#componentdidcatch) to add some extra logic, for example, to log the error to an analytics service.
-```js {7-10,12-19}
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+
+```js {9-12,14-27}
+import * as React from 'react';
+
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
@@ -1286,12 +1290,18 @@ class ErrorBoundary extends React.Component {
}
componentDidCatch(error, info) {
- // Example "componentStack":
- // in ComponentThatThrows (created by App)
- // in ErrorBoundary (created by App)
- // in div (created by App)
- // in App
- logErrorToMyService(error, info.componentStack);
+ logErrorToMyService(
+ error,
+ // Example "componentStack":
+ // in ComponentThatThrows (created by App)
+ // in ErrorBoundary (created by App)
+ // in div (created by App)
+ // in App
+ info.componentStack,
+ // Only available in react@experimental.
+ // Warning: Owner Stack is not available in production.
+ React.captureOwnerStack(),
+ );
}
render() {
diff --git a/src/content/reference/react/captureOwnerStack.md b/src/content/reference/react/captureOwnerStack.md
index a158ca4291a..87665303586 100644
--- a/src/content/reference/react/captureOwnerStack.md
+++ b/src/content/reference/react/captureOwnerStack.md
@@ -184,6 +184,15 @@ Neither `Navigation` nor `legend` are in the stack at all since it's only a sibl
## Usage {/*usage*/}
+### Improve error reporting {/*improve-error-reporting*/}
+
+Check out the following example to see how to use `captureOwnerStack` to improve error reporting:
+
+- [createRoot usage: Show a dialog for uncaught errors
+](/reference/react-dom/client/createRoot#show-a-dialog-for-uncaught-errors)
+- [createRoot usage: Displaying Error Boundary errors](/reference/react-dom/client/createRoot#show-a-dialog-for-uncaught-errors)
+- [createRoot usage: Displaying a dialog for recoverable errors](/reference/react-dom/client/createRoot#displaying-a-dialog-for-recoverable-errors)
+
### Expanding error stacks {/*expanding-error-stacks*/}
In addition to the stack trace of the error itself, you can use `captureOwnerStack` to append the Owner Stack.
From 5b8bc8771822011e6c48698feb50980962b86f97 Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Thu, 23 Jan 2025 14:11:59 +0100
Subject: [PATCH 11/20] experimental -> Canary
---
src/components/MDX/MDXComponents.tsx | 15 ---------------
.../reference/react-dom/client/createRoot.md | 18 +++++++++---------
.../reference/react-dom/client/hydrateRoot.md | 18 +++++++++---------
src/content/reference/react/Component.md | 4 ++--
.../reference/react/captureOwnerStack.md | 12 +++---------
5 files changed, 23 insertions(+), 44 deletions(-)
diff --git a/src/components/MDX/MDXComponents.tsx b/src/components/MDX/MDXComponents.tsx
index c92b96c4bd3..f24fac5988e 100644
--- a/src/components/MDX/MDXComponents.tsx
+++ b/src/components/MDX/MDXComponents.tsx
@@ -120,20 +120,6 @@ const CanaryBadge = ({title}: {title: string}) => (
);
-const ExperimentalBadge = ({title}: {title: string}) => (
-
-
- Experimental only
-
-);
-
const NextMajorBadge = ({title}: {title: string}) => (
onUncaughtError option is a function called wi
1. The error that was thrown.
2. An errorInfo object that contains the componentStack of the error.
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
You can use the `onUncaughtError` root option to display error dialogs:
@@ -543,7 +543,7 @@ export function reportRecoverableError({error, cause, componentStack, ownerStack
```
```js src/index.js active
-// captureOwnerStack is only available in react@experimental.
+// captureOwnerStack is only available in react@canary.
import { captureOwnerStack } from 'react';
import { createRoot } from "react-dom/client";
import App from "./App.js";
@@ -613,7 +613,7 @@ export default function App() {
By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option to handle errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary):
```js [[1, 8, "onCaughtError"], [2, 8, "error", 1], [3, 8, "errorInfo"], [4, 12, "componentStack"], [5, 13, "captureOwnerStack()"]]
-// captureOwnerStack is only available in react@experimental.
+// captureOwnerStack is only available in react@canary.
import { captureOwnerStack } from 'react';
import { createRoot } from 'react-dom/client';
@@ -638,7 +638,7 @@ The onCaughtError option is a function called with
1. The error that was caught by the boundary.
2. An errorInfo object that contains the componentStack of the error.
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging:
@@ -807,7 +807,7 @@ export function reportRecoverableError({error, cause, componentStack, ownerStack
```
```js src/index.js active
-// captureOwnerStack is only available in react@experimental.
+// captureOwnerStack is only available in react@canary.
import {captureOwnerStack} from 'react';
import { createRoot } from "react-dom/client";
import App from "./App.js";
@@ -904,7 +904,7 @@ function Throw({error}) {
React may automatically render a component a second time to attempt to recover from an error thrown in render. If successful, React will log a recoverable error to the console to notify the developer. To override this behavior, you can provide the optional `onRecoverableError` root option:
```js [[1, 8, "onRecoverableError"], [2, 8, "error", 1], [3, 12, "error.cause"], [4, 8, "errorInfo"], [5, 13, "componentStack"], [6, 14, "captureOwnerStack()"]]
-// captureOwnerStack is only available in react@experimental.
+// captureOwnerStack is only available in react@canary.
import { captureOwnerStack } from 'react';
import { createRoot } from 'react-dom/client';
@@ -930,7 +930,7 @@ The onRecoverableError option is a function called
1. The error that React throws. Some errors may include the original cause as error.cause.
2. An errorInfo object that contains the componentStack of the error.
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
You can use the `onRecoverableError` root option to display error dialogs:
@@ -1099,7 +1099,7 @@ export function reportRecoverableError({error, cause, componentStack, ownerStack
```
```js src/index.js active
-// captureOwnerStack is only available in react@experimental.
+// captureOwnerStack is only available in react@canary.
import {captureOwnerStack} from 'react'
import { createRoot } from "react-dom/client";
import App from "./App.js";
diff --git a/src/content/reference/react-dom/client/hydrateRoot.md b/src/content/reference/react-dom/client/hydrateRoot.md
index 914337e371b..1f47b18514e 100644
--- a/src/content/reference/react-dom/client/hydrateRoot.md
+++ b/src/content/reference/react-dom/client/hydrateRoot.md
@@ -379,7 +379,7 @@ It is uncommon to call [`root.render`](#root-render) on a hydrated root. Usually
By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option:
```js [[1, 9, "onUncaughtError"], [2, 9, "error", 1], [3, 9, "errorInfo"], [4, 13, "componentStack"], [5, 14, "captureOwnerStack()"]]
-// captureOwnerStack is only available in react@experimental.
+// captureOwnerStack is only available in react@canary.
import { captureOwnerStack } from 'react';
import { hydrateRoot } from 'react-dom/client';
@@ -404,7 +404,7 @@ The onUncaughtError option is a function called wi
1. The error that was thrown.
2. An errorInfo object that contains the componentStack of the error.
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
You can use the `onUncaughtError` root option to display error dialogs:
@@ -576,7 +576,7 @@ export function reportRecoverableError({error, cause, componentStack, ownerStack
```
```js src/index.js active
-// captureOwnerStack is only available in react@experimental.
+// captureOwnerStack is only available in react@canary.
import { captureOwnerStack } from 'react';
import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
@@ -646,7 +646,7 @@ export default function App() {
By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option for errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary):
```js [[1, 9, "onCaughtError"], [2, 9, "error", 1], [3, 9, "errorInfo"], [4, 13, "componentStack"], [5, 14, "captureOwnerStack()"]]
-// captureOwnerStack is only available in react@experimental.
+// captureOwnerStack is only available in react@canary.
import { captureOwnerStack } from 'react';
import { hydrateRoot } from 'react-dom/client';
@@ -671,7 +671,7 @@ The onCaughtError option is a function called with
1. The error that was caught by the boundary.
2. An errorInfo object that contains the componentStack of the error.
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging:
@@ -843,7 +843,7 @@ export function reportRecoverableError({error, cause, componentStack}) {
```
```js src/index.js active
-// captureOwnerStack is only available in react@experimental.
+// captureOwnerStack is only available in react@canary.
import {captureOwnerStack} from 'react';
import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
@@ -939,7 +939,7 @@ function Throw({error}) {
When React encounters a hydration mismatch, it will automatically attempt to recover by rendering on the client. By default, React will log hydration mismatch errors to `console.error`. To override this behavior, you can provide the optional `onRecoverableError` root option:
```js [[1, 9, "onRecoverableError"], [2, 9, "error", 1], [3, 13, "error.cause", 1], [4, 9, "errorInfo"], [5, 14, "componentStack"], [6, 15, "captureOwnerStack()"]]
-// captureOwnerStack is only available in react@experimental.
+// captureOwnerStack is only available in react@canary.
import { captureOwnerStack } from 'react';
import { hydrateRoot } from 'react-dom/client';
@@ -965,7 +965,7 @@ The onRecoverableError option is a function called
1. The error React throws. Some errors may include the original cause as error.cause.
2. An errorInfo object that contains the componentStack of the error.
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
You can use the `onRecoverableError` root option to display error dialogs for hydration mismatches:
@@ -1137,7 +1137,7 @@ export function reportRecoverableError({error, cause, componentStack, ownerStack
```
```js src/index.js active
-// captureOwnerStack is only available in react@experimental.
+// captureOwnerStack is only available in react@canary.
import {captureOwnerStack} from 'react'
import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
diff --git a/src/content/reference/react/Component.md b/src/content/reference/react/Component.md
index 5e8c8d9679f..0821d159392 100644
--- a/src/content/reference/react/Component.md
+++ b/src/content/reference/react/Component.md
@@ -1273,7 +1273,7 @@ By default, if your application throws an error during rendering, React will rem
To implement an error boundary component, you need to provide [`static getDerivedStateFromError`](#static-getderivedstatefromerror) which lets you update state in response to an error and display an error message to the user. You can also optionally implement [`componentDidCatch`](#componentdidcatch) to add some extra logic, for example, to log the error to an analytics service.
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
+ With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
```js {9-12,14-27}
import * as React from 'react';
@@ -1298,7 +1298,7 @@ class ErrorBoundary extends React.Component {
// in div (created by App)
// in App
info.componentStack,
- // Only available in react@experimental.
+ // Only available in react@canary.
// Warning: Owner Stack is not available in production.
React.captureOwnerStack(),
);
diff --git a/src/content/reference/react/captureOwnerStack.md b/src/content/reference/react/captureOwnerStack.md
index 87665303586..57ceb54b322 100644
--- a/src/content/reference/react/captureOwnerStack.md
+++ b/src/content/reference/react/captureOwnerStack.md
@@ -2,17 +2,11 @@
title: captureOwnerStack
---
-
+
-**This API is experimental and is not available in a stable version of React yet.**
+The `captureOwnerStack` API is currently only available in React's Canary and experimental channels. Learn more about [React's release channels here](/community/versioning-policy#all-release-channels).
-You can try it by upgrading React packages to the most recent experimental version:
-
-- `react@experimental`
-
-Experimental versions of React may contain bugs. Don't use them in production.
-
-
+
From 2d0da579b9b0ce88bae31dfed400e1c592404681 Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Thu, 23 Jan 2025 14:19:57 +0100
Subject: [PATCH 12/20] repeat when owner stacks are available
---
src/content/reference/react/captureOwnerStack.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/content/reference/react/captureOwnerStack.md b/src/content/reference/react/captureOwnerStack.md
index 57ceb54b322..027c3979dba 100644
--- a/src/content/reference/react/captureOwnerStack.md
+++ b/src/content/reference/react/captureOwnerStack.md
@@ -47,8 +47,7 @@ function Component() {
`captureOwnerStack` returns `string | null`.
-If no Owner Stack is available, it returns an empty string.
-Outside of development builds, `null` is returned.
+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)).
#### Caveats {/*caveats*/}
From 9806ea7711317473c813eedf8d951338349445d0 Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Thu, 23 Jan 2025 14:22:20 +0100
Subject: [PATCH 13/20] Work around single newlines being rendered as newlines
Only double newlines usually end up as a newline in rendered markdown.
---
src/content/reference/react/captureOwnerStack.md | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/content/reference/react/captureOwnerStack.md b/src/content/reference/react/captureOwnerStack.md
index 027c3979dba..73db2cf9d71 100644
--- a/src/content/reference/react/captureOwnerStack.md
+++ b/src/content/reference/react/captureOwnerStack.md
@@ -285,10 +285,8 @@ const root = createRoot(document.getElementById('root'), {
### The Owner Stack is `null` {/*the-owner-stack-is-null*/}
-`captureOwnerStack` was called outside of development builds.
-For performance reasons, React will not keep track of Owners in production.
+`captureOwnerStack` was called outside of development builds. For performance reasons, React will not keep track of Owners in production.
### The Owner Stack is empty {/*the-owner-stack-is-empty*/}
-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.
+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.
From 66562e88cccd709fa5af6c111f876195e437f43b Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Thu, 23 Jan 2025 14:24:30 +0100
Subject: [PATCH 14/20] Ensure comments don't hscroll
---
.../reference/react/captureOwnerStack.md | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/content/reference/react/captureOwnerStack.md b/src/content/reference/react/captureOwnerStack.md
index 73db2cf9d71..df5c3e85c85 100644
--- a/src/content/reference/react/captureOwnerStack.md
+++ b/src/content/reference/react/captureOwnerStack.md
@@ -105,8 +105,10 @@ import './styles.css';
createRoot(document.createElement('div'), {
onUncaughtError: (error, errorInfo) => {
- // The stacks are logged instead of showing them in the UI directly to highlight that browsers will apply sourcemaps to the logged stacks.
- // Note that sourcemapping is only applied in the real browser console not in the fake one displayed on this page.
+ // The stacks are logged instead of showing them in the UI directly to
+ // highlight that browsers will apply sourcemaps to the logged stacks.
+ // Note that sourcemapping is only applied in the real browser console not
+ // in the fake one displayed on this page.
console.log(errorInfo.componentStack);
console.log(captureOwnerStack());
},
@@ -235,14 +237,18 @@ const root = createRoot(document.getElementById('root'), {
if (process.env.NODE_ENV !== 'production') {
const ownerStack = captureOwnerStack();
error.stack =
- // The stack is only split because these sandboxes don't implement ignore-listing 3rd party frames via sourcemaps.
- // A framework would ignore-list stackframes from React via sourcemaps and then you could just `error.stack += ownerStack`.
+ // The stack is only split because these sandboxes don't implement
+ // ignore-listing 3rd party frames via sourcemaps.
+ // A framework would ignore-list stackframes from React via sourcemaps
+ // and then you could just `error.stack += ownerStack`.
// To learn more about ignore-listing see https://developer.chrome.com/docs/devtools/x-google-ignore-list
error.stack.split('\n at react-stack-bottom-frame')[0] + ownerStack;
}
- // The stacks are logged instead of showing them in the UI directly to highlight that browsers will apply sourcemaps to the logged stacks.
- // Note that sourcemapping is only applied in the real browser console not in the fake one displayed on this page.
+ // The stacks are logged instead of showing them in the UI directly to
+ // highlight that browsers will apply sourcemaps to the logged stacks.
+ // Note that sourcemapping is only applied in the real browser console not
+ // in the fake one displayed on this page.
console.error('Uncaught', error);
},
}).render();
From a2fab1db9f4fc3d1da0af98cac705428a9839bbe Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Thu, 23 Jan 2025 14:29:27 +0100
Subject: [PATCH 15/20] Troubleshooting empty stacks sandbox
---
.../reference/react/captureOwnerStack.md | 258 +++++++++++++++++-
1 file changed, 250 insertions(+), 8 deletions(-)
diff --git a/src/content/reference/react/captureOwnerStack.md b/src/content/reference/react/captureOwnerStack.md
index df5c3e85c85..e967019ce58 100644
--- a/src/content/reference/react/captureOwnerStack.md
+++ b/src/content/reference/react/captureOwnerStack.md
@@ -47,7 +47,13 @@ function Component() {
`captureOwnerStack` returns `string | null`.
-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)).
+Owner Stacks are available in
+- Component render
+- Effects (e.g. `useEffect`)
+- React's event handlers (e.g. ``)
+- React error handlers ([React Root options](/reference/react-dom/client/createRoot#parameters) `onCaughtError`, `onRecoverableError`, and `onUncaughtError`)
+
+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)).
#### Caveats {/*caveats*/}
@@ -109,6 +115,7 @@ createRoot(document.createElement('div'), {
// highlight that browsers will apply sourcemaps to the logged stacks.
// Note that sourcemapping is only applied in the real browser console not
// in the fake one displayed on this page.
+ // Press "fork" to be able to view the sourcemapped stack in a real console.
console.log(errorInfo.componentStack);
console.log(captureOwnerStack());
},
@@ -179,14 +186,205 @@ Neither `Navigation` nor `legend` are in the stack at all since it's only a sibl
## Usage {/*usage*/}
-### Improve error reporting {/*improve-error-reporting*/}
+### Enhance a custom error overlay {/*enhance-a-custom-error-overlay*/}
+
+```js [[1, 5, "console.error"], [4, 7, "captureOwnerStack"]]
+import { captureOwnerStack } from "react";
+import { instrumentedConsoleError } from "./errorOverlay";
+
+const originalConsoleError = console.error;
+console.error = function patchedConsoleError(...args) {
+ originalConsoleError.apply(console, args);
+ const ownerStack = captureOwnerStack();
+ onConsoleError({
+ // Keep in mind that in a real application, console.error can be
+ // called with multiple arguments which you should account for.
+ consoleMessage: args[0],
+ ownerStack,
+ });
+};
+```
+
+If you intercept `console.error` calls to highlight them in an error overlay, you can call `captureOwnerStack` to include the Owner Stack.
+
+
+
+```css src/styles.css
+* {
+ box-sizing: border-box;
+}
+
+body {
+ font-family: sans-serif;
+ margin: 20px;
+ padding: 0;
+}
+
+h1 {
+ margin-top: 0;
+ font-size: 22px;
+}
+
+h2 {
+ margin-top: 0;
+ font-size: 20px;
+}
+
+code {
+ font-size: 1.2em;
+}
+
+ul {
+ padding-inline-start: 20px;
+}
+
+label, button { display: block; margin-bottom: 20px; }
+html, body { min-height: 300px; }
+
+#error-dialog {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: white;
+ padding: 15px;
+ opacity: 0.9;
+ text-wrap: wrap;
+ overflow: scroll;
+}
+
+.text-red {
+ color: red;
+}
+
+.-mb-20 {
+ margin-bottom: -20px;
+}
+
+.mb-0 {
+ margin-bottom: 0;
+}
+
+.mb-10 {
+ margin-bottom: 10px;
+}
+
+pre {
+ text-wrap: wrap;
+}
+
+pre.nowrap {
+ text-wrap: nowrap;
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```html public/index.html hidden
+
+
+
+ My app
+
+
+
+
+
Error
+
+
+
+
Owner stack:
+
+
+
+
+
+
+
+
+```
+
+```js src/errorOverlay.js
+
+export function onConsoleError({ consoleMessage, ownerStack }) {
+ const errorDialog = document.getElementById("error-dialog");
+ const errorBody = document.getElementById("error-body");
+ const errorOwnerStack = document.getElementById("error-owner-stack");
+ const errorStack = document.getElementById("error-stack");
+
+ // Display console.error() message
+ errorBody.innerText = consoleMessage;
+
+ // Display owner stack
+ errorOwnerStack.innerText = ownerStack;
+
+ // Show the dialog
+ errorDialog.classList.remove("hidden");
+}
+```
+
+```js src/index.js active
+import { captureOwnerStack } from "react";
+import { createRoot } from "react-dom/client";
+import App from './App';
+import { onConsoleError } from "./errorOverlay";
+import './styles.css';
+
+const originalConsoleError = console.error;
+console.error = function patchedConsoleError(...args) {
+ originalConsoleError.apply(console, args);
+ const ownerStack = captureOwnerStack();
+ onConsoleError({
+ // Keep in mind that in a real application, console.error can be
+ // called with multiple arguments which you should account for.
+ consoleMessage: args[0],
+ ownerStack,
+ });
+};
+
+const container = document.getElementById("root");
+createRoot(container).render();
+```
+
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "experimental",
+ "react-dom": "experimental",
+ "react-scripts": "latest"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test --env=jsdom",
+ "eject": "react-scripts eject"
+ }
+}
+```
+
+```js src/App.js
+function Component() {
+ return ;
+}
-Check out the following example to see how to use `captureOwnerStack` to improve error reporting:
+export default function App() {
+ return ;
+}
+```
-- [createRoot usage: Show a dialog for uncaught errors
-](/reference/react-dom/client/createRoot#show-a-dialog-for-uncaught-errors)
-- [createRoot usage: Displaying Error Boundary errors](/reference/react-dom/client/createRoot#show-a-dialog-for-uncaught-errors)
-- [createRoot usage: Displaying a dialog for recoverable errors](/reference/react-dom/client/createRoot#displaying-a-dialog-for-recoverable-errors)
+
### Expanding error stacks {/*expanding-error-stacks*/}
@@ -295,4 +493,48 @@ const root = createRoot(document.getElementById('root'), {
### The Owner Stack is empty {/*the-owner-stack-is-empty*/}
-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.
+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.
+
+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.
+
+
+```js
+import {captureOwnerStack, useEffect} from 'react';
+
+export default function App() {
+ useEffect(() => {
+ // Should call `captureOwnerStack` here.
+ function handleEvent() {
+ // Calling it in a custom DOM event handler is too late.
+ // The Owner Stack will be empty at this point.
+ console.log('Owner Stack: ', captureOwnerStack());
+ }
+
+ document.addEventListener('click', handleEvent);
+
+ return () => {
+ document.removeEventListener('click', handleEvent);
+ }
+ })
+
+ return ;
+}
+```
+
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "experimental",
+ "react-dom": "experimental",
+ "react-scripts": "latest"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test --env=jsdom",
+ "eject": "react-scripts eject"
+ }
+}
+```
+
+
\ No newline at end of file
From f4c02461f3966fcdeba02d6fa776018e0893f43a Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Thu, 23 Jan 2025 15:21:46 +0100
Subject: [PATCH 16/20] Align codestep previews with sandboxes
---
.../reference/react-dom/client/createRoot.md | 87 +++++++++----------
.../reference/react-dom/client/hydrateRoot.md | 86 +++++++++---------
2 files changed, 83 insertions(+), 90 deletions(-)
diff --git a/src/content/reference/react-dom/client/createRoot.md b/src/content/reference/react-dom/client/createRoot.md
index 03161d98e85..1bcee74298a 100644
--- a/src/content/reference/react-dom/client/createRoot.md
+++ b/src/content/reference/react-dom/client/createRoot.md
@@ -348,25 +348,24 @@ It is uncommon to call `render` multiple times. Usually, your components will [u
By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option:
-```js [[1, 8, "onUncaughtError"], [2, 8, "error", 1], [3, 8, "errorInfo"], [4, 12, "componentStack"], [5, 13, "captureOwnerStack()"]]
+```js [[1, 8, "onUncaughtError"], [2, 8, "error", 1], [3, 8, "errorInfo"], [4, 12, "componentStack", 15], [5, 13, "captureOwnerStack()"]]
// captureOwnerStack is only available in react@canary.
-import { captureOwnerStack } from 'react';
-import { createRoot } from 'react-dom/client';
+import { captureOwnerStack } from "react";
+import { createRoot } from "react-dom/client";
+import { reportUncaughtError } from "./reportError";
-const root = createRoot(
- document.getElementById('root'),
- {
- onUncaughtError: (error, errorInfo) => {
- console.error(
- 'Uncaught error',
+const container = document.getElementById("root");
+const root = createRoot(container, {
+ onUncaughtError: (error, errorInfo) => {
+ if (error.message !== "Known error") {
+ reportUncaughtError({
error,
- errorInfo.componentStack,
- captureOwnerStack()
- );
+ componentStack: errorInfo.componentStack,
+ ownerStack: captureOwnerStack(),
+ });
}
- }
-);
-root.render();
+ },
+});
```
The onUncaughtError option is a function called with two arguments:
@@ -612,25 +611,24 @@ export default function App() {
By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option to handle errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary):
-```js [[1, 8, "onCaughtError"], [2, 8, "error", 1], [3, 8, "errorInfo"], [4, 12, "componentStack"], [5, 13, "captureOwnerStack()"]]
+```js [[1, 8, "onCaughtError"], [2, 8, "error", 1], [3, 8, "errorInfo"], [4, 12, "componentStack", 15], [5, 13, "captureOwnerStack()"]]
// captureOwnerStack is only available in react@canary.
-import { captureOwnerStack } from 'react';
-import { createRoot } from 'react-dom/client';
+import {captureOwnerStack} from 'react';
+import { createRoot } from "react-dom/client";
+import {reportCaughtError} from "./reportError";
-const root = createRoot(
- document.getElementById('root'),
- {
- onCaughtError: (error, errorInfo) => {
- console.error(
- 'Caught error',
- error,
- errorInfo.componentStack,
+const container = document.getElementById("root");
+const root = createRoot(container, {
+ onCaughtError: (error, errorInfo) => {
+ if (error.message !== 'Known error') {
+ reportCaughtError({
+ error,
+ componentStack: errorInfo.componentStack,
ownerStack: captureOwnerStack()
- );
+ });
}
}
-);
-root.render();
+});
```
The onCaughtError option is a function called with two arguments:
@@ -903,26 +901,23 @@ function Throw({error}) {
React may automatically render a component a second time to attempt to recover from an error thrown in render. If successful, React will log a recoverable error to the console to notify the developer. To override this behavior, you can provide the optional `onRecoverableError` root option:
-```js [[1, 8, "onRecoverableError"], [2, 8, "error", 1], [3, 12, "error.cause"], [4, 8, "errorInfo"], [5, 13, "componentStack"], [6, 14, "captureOwnerStack()"]]
+```js [[1, 8, "onRecoverableError"], [2, 8, "error", 1], [3, 11, "error.cause"], [4, 8, "errorInfo"], [5, 12, "componentStack", 15], [6, 13, "captureOwnerStack()"]]
// captureOwnerStack is only available in react@canary.
-import { captureOwnerStack } from 'react';
-import { createRoot } from 'react-dom/client';
+import {captureOwnerStack} from 'react'
+import { createRoot } from "react-dom/client";
+import {reportRecoverableError} from "./reportError";
-const root = createRoot(
- document.getElementById('root'),
- {
- onRecoverableError: (error, errorInfo) => {
- console.error(
- 'Recoverable error',
- error,
- error.cause,
- errorInfo.componentStack,
- captureOwnerStack(),
- );
- }
+const container = document.getElementById("root");
+const root = createRoot(container, {
+ onRecoverableError: (error, errorInfo) => {
+ reportRecoverableError({
+ error,
+ cause: error.cause,
+ componentStack: errorInfo.componentStack,
+ ownerStack: captureOwnerStack(),
+ });
}
-);
-root.render();
+});
```
The onRecoverableError option is a function called with two arguments:
diff --git a/src/content/reference/react-dom/client/hydrateRoot.md b/src/content/reference/react-dom/client/hydrateRoot.md
index 1f47b18514e..83d95a82cd8 100644
--- a/src/content/reference/react-dom/client/hydrateRoot.md
+++ b/src/content/reference/react-dom/client/hydrateRoot.md
@@ -378,25 +378,25 @@ It is uncommon to call [`root.render`](#root-render) on a hydrated root. Usually
By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option:
-```js [[1, 9, "onUncaughtError"], [2, 9, "error", 1], [3, 9, "errorInfo"], [4, 13, "componentStack"], [5, 14, "captureOwnerStack()"]]
+```js [[1, 9, "onUncaughtError"], [2, 9, "error", 1], [3, 9, "errorInfo"], [4, 13, "componentStack", 15], [5, 14, "captureOwnerStack()"]]
// captureOwnerStack is only available in react@canary.
import { captureOwnerStack } from 'react';
-import { hydrateRoot } from 'react-dom/client';
+import { hydrateRoot } from "react-dom/client";
+import App from "./App";
+import {reportUncaughtError} from "./reportError";
-hydrateRoot(
- document.getElementById('root'),
- ,
- {
- onUncaughtError: (error, errorInfo) => {
- console.error(
- 'Uncaught error',
+const container = document.getElementById("root");
+hydrateRoot(container, , {
+ onUncaughtError: (error, errorInfo) => {
+ if (error.message !== 'Known error') {
+ reportUncaughtError({
error,
- errorInfo.componentStack,
- captureOwnerStack()
- );
+ componentStack: errorInfo.componentStack,
+ ownerStack: captureOwnerStack()
+ });
}
}
-);
+});
```
The onUncaughtError option is a function called with two arguments:
@@ -579,7 +579,7 @@ export function reportRecoverableError({error, cause, componentStack, ownerStack
// captureOwnerStack is only available in react@canary.
import { captureOwnerStack } from 'react';
import { hydrateRoot } from "react-dom/client";
-import App from "./App.js";
+import App from "./App";
import {reportUncaughtError} from "./reportError";
import "./styles.css";
import {renderToString} from 'react-dom/server';
@@ -645,25 +645,25 @@ export default function App() {
By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option for errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary):
-```js [[1, 9, "onCaughtError"], [2, 9, "error", 1], [3, 9, "errorInfo"], [4, 13, "componentStack"], [5, 14, "captureOwnerStack()"]]
+```js [[1, 9, "onCaughtError"], [2, 9, "error", 1], [3, 9, "errorInfo"], [4, 13, "componentStack", 15], [5, 14, "captureOwnerStack()"]]
// captureOwnerStack is only available in react@canary.
-import { captureOwnerStack } from 'react';
-import { hydrateRoot } from 'react-dom/client';
+import {captureOwnerStack} from 'react';
+import { hydrateRoot } from "react-dom/client";
+import App from "./App.js";
+import {reportCaughtError} from "./reportError";
-hydrateRoot(
- document.getElementById('root'),
- ,
- {
- onCaughtError: (error, errorInfo) => {
- console.error(
- 'Caught error',
+const container = document.getElementById("root");
+hydrateRoot(container, , {
+ onCaughtError: (error, errorInfo) => {
+ if (error.message !== 'Known error') {
+ reportCaughtError({
error,
- errorInfo.componentStack,
+ componentStack: errorInfo.componentStack,
ownerStack: captureOwnerStack()
- );
+ });
}
}
-);
+});
```
The onCaughtError option is a function called with two arguments:
@@ -938,26 +938,24 @@ function Throw({error}) {
When React encounters a hydration mismatch, it will automatically attempt to recover by rendering on the client. By default, React will log hydration mismatch errors to `console.error`. To override this behavior, you can provide the optional `onRecoverableError` root option:
-```js [[1, 9, "onRecoverableError"], [2, 9, "error", 1], [3, 13, "error.cause", 1], [4, 9, "errorInfo"], [5, 14, "componentStack"], [6, 15, "captureOwnerStack()"]]
+```js [[1, 9, "onRecoverableError"], [2, 9, "error", 1], [3, 12, "error.cause", 1], [4, 9, "errorInfo"], [5, 13, "componentStack", 15], [6, 14, "captureOwnerStack()"]]
// captureOwnerStack is only available in react@canary.
-import { captureOwnerStack } from 'react';
-import { hydrateRoot } from 'react-dom/client';
+import {captureOwnerStack} from 'react'
+import { hydrateRoot } from "react-dom/client";
+import App from "./App.js";
+import {reportRecoverableError} from "./reportError";
-const root = hydrateRoot(
- document.getElementById('root'),
- ,
- {
- onRecoverableError: (error, errorInfo) => {
- console.error(
- 'Caught error',
- error,
- error.cause,
- errorInfo.componentStack,
- captureOwnerStack()
- );
- }
+const container = document.getElementById("root");
+const root = hydrateRoot(container, , {
+ onRecoverableError: (error, errorInfo) => {
+ reportRecoverableError({
+ error,
+ cause: error.cause,
+ componentStack: errorInfo.componentStack,
+ ownerStack: captureOwnerStack()
+ });
}
-);
+});
```
The onRecoverableError option is a function called with two arguments:
From 7570ac6640d6c7dddea4cc4d61445aa00ce0d4f3 Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Thu, 23 Jan 2025 15:31:07 +0100
Subject: [PATCH 17/20] Remove console patching section
It's just a (bad) polyfill for what RDT is doing. Better to lean on RDT for owner stacks in console calls.
---
.../reference/react/captureOwnerStack.md | 99 -------------------
1 file changed, 99 deletions(-)
diff --git a/src/content/reference/react/captureOwnerStack.md b/src/content/reference/react/captureOwnerStack.md
index e967019ce58..7eaba4eb5dc 100644
--- a/src/content/reference/react/captureOwnerStack.md
+++ b/src/content/reference/react/captureOwnerStack.md
@@ -386,105 +386,6 @@ export default function App() {
-### Expanding error stacks {/*expanding-error-stacks*/}
-
-In addition to the stack trace of the error itself, you can use `captureOwnerStack` to append the Owner Stack.
-This can help trace the error especially when the error is caused by props. The Owner Stack helps trace the flow of props.
-
-
-```js src/index.js [[1, 8, "error.stack"], [2, 7, "captureOwnerStack()"]]
-import {captureOwnerStack} from 'react';
-import {createRoot} from 'react-dom/client';
-
-const root = createRoot(document.getElementById('root'), {
- onUncaughtError: (error, errorInfo) => {
- if (process.env.NODE_ENV !== 'production') {
- const ownerStack = captureOwnerStack();
- error.stack += ownerStack;
- }
-
- console.error('Uncaught', error);
- },
-}).render();
-```
-
-
-
-```js
-function useCustomHook() {
- throw new Error('Boom!');
-}
-
-function Component() {
- useCustomHook();
-}
-
-export default function App() {
- return ;
-}
-```
-
-```js src/index.js
-import {captureOwnerStack} from 'react';
-import {createRoot} from 'react-dom/client';
-import App from './App.js';
-import './styles.css';
-
-const root = createRoot(document.getElementById('root'), {
- onUncaughtError: (error, errorInfo) => {
- if (process.env.NODE_ENV !== 'production') {
- const ownerStack = captureOwnerStack();
- error.stack =
- // The stack is only split because these sandboxes don't implement
- // ignore-listing 3rd party frames via sourcemaps.
- // A framework would ignore-list stackframes from React via sourcemaps
- // and then you could just `error.stack += ownerStack`.
- // To learn more about ignore-listing see https://developer.chrome.com/docs/devtools/x-google-ignore-list
- error.stack.split('\n at react-stack-bottom-frame')[0] + ownerStack;
- }
-
- // The stacks are logged instead of showing them in the UI directly to
- // highlight that browsers will apply sourcemaps to the logged stacks.
- // Note that sourcemapping is only applied in the real browser console not
- // in the fake one displayed on this page.
- console.error('Uncaught', error);
- },
-}).render();
-```
-
-```json package.json hidden
-{
- "dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
- "react-scripts": "latest"
- },
- "scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test --env=jsdom",
- "eject": "react-scripts eject"
- }
-}
-```
-
-```html public/index.html hidden
-
-
-
-
-
- Document
-
-
-
Check the console output.
-
-
-
-```
-
-
-
## Troubleshooting {/*troubleshooting*/}
### The Owner Stack is `null` {/*the-owner-stack-is-null*/}
From c76a3d34bad8a26d5b514d09e0666231bf94248e Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Mon, 3 Feb 2025 17:45:49 +0100
Subject: [PATCH 18/20] Rework error overlay illustration
root error handlers for prod only
captureOwnerStack illustration in patched console.error
---
.../reference/react-dom/client/createRoot.md | 817 ++----------------
.../reference/react-dom/client/hydrateRoot.md | 816 ++---------------
2 files changed, 120 insertions(+), 1513 deletions(-)
diff --git a/src/content/reference/react-dom/client/createRoot.md b/src/content/reference/react-dom/client/createRoot.md
index 1bcee74298a..0a393394988 100644
--- a/src/content/reference/react-dom/client/createRoot.md
+++ b/src/content/reference/react-dom/client/createRoot.md
@@ -344,830 +344,127 @@ export default function App({counter}) {
It is uncommon to call `render` multiple times. Usually, your components will [update state](/reference/react/useState) instead.
-### Show a dialog for uncaught errors {/*show-a-dialog-for-uncaught-errors*/}
+### Error logging in production {/*error-logging-in-production*/}
-By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option:
+By default, React will log all errors to the console. To implement your own error reporting, you can provide the optional error handler root options `onUncaughtError`, `onCaughtError` and `onRecoverableError`:
-```js [[1, 8, "onUncaughtError"], [2, 8, "error", 1], [3, 8, "errorInfo"], [4, 12, "componentStack", 15], [5, 13, "captureOwnerStack()"]]
-// captureOwnerStack is only available in react@canary.
-import { captureOwnerStack } from "react";
+```js [[1, 6, "onCaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack", 15]]
import { createRoot } from "react-dom/client";
-import { reportUncaughtError } from "./reportError";
+import { reportCaughtError } from "./reportError";
const container = document.getElementById("root");
const root = createRoot(container, {
- onUncaughtError: (error, errorInfo) => {
+ onCaughtError: (error, errorInfo) => {
if (error.message !== "Known error") {
- reportUncaughtError({
+ reportCaughtError({
error,
componentStack: errorInfo.componentStack,
- ownerStack: captureOwnerStack(),
});
}
},
});
```
-The onUncaughtError option is a function called with two arguments:
+The onCaughtError option is a function called with two arguments:
1. The error that was thrown.
2. An errorInfo object that contains the componentStack of the error.
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
-
-You can use the `onUncaughtError` root option to display error dialogs:
+Together with `onUncaughtError` and `onRecoverableError`, you can can implement your own error reporting system:
-```html public/index.html hidden
-
-
-
- My app
-
-
-
-
-
-
-
-
-
-
-
-
Component stack:
-
-
Owner stack:
-
-
Call stack:
-
-
-
Caused by:
-
-
-
-
-
This error is not dismissible.
-
-
-
-
-
-```
-
-```css src/styles.css active
-label, button { display: block; margin-bottom: 20px; }
-html, body { min-height: 300px; }
-
-#error-dialog {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- background-color: white;
- padding: 15px;
- opacity: 0.9;
- text-wrap: wrap;
- overflow: scroll;
-}
-
-.text-red {
- color: red;
-}
-
-.-mb-20 {
- margin-bottom: -20px;
-}
-
-.mb-0 {
- margin-bottom: 0;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-pre {
- text-wrap: wrap;
-}
-
-pre.nowrap {
- text-wrap: nowrap;
+```js src/reportError.js
+function reportError({ type, error, errorInfo }) {
+ // The specific implementation is up to you.
+ // `console.error()` is only used for demonstration purposes.
+ console.error(type, error, "Component Stack: ");
+ console.error("Component Stack: ", errorInfo.componentStack);
}
-.hidden {
- display: none;
-}
-```
-
-```js src/reportError.js hidden
-function reportError({ title, error, componentStack, ownerStack, dismissable }) {
- const errorDialog = document.getElementById("error-dialog");
- const errorTitle = document.getElementById("error-title");
- const errorMessage = document.getElementById("error-message");
- const errorBody = document.getElementById("error-body");
- const errorComponentStack = document.getElementById("error-component-stack");
- const errorOwnerStack = document.getElementById("error-owner-stack");
- const errorStack = document.getElementById("error-stack");
- const errorClose = document.getElementById("error-close");
- const errorCause = document.getElementById("error-cause");
- const errorCauseMessage = document.getElementById("error-cause-message");
- const errorCauseStack = document.getElementById("error-cause-stack");
- const errorNotDismissible = document.getElementById("error-not-dismissible");
-
- // Set the title
- errorTitle.innerText = title;
-
- // Display error message and body
- const [heading, body] = error.message.split(/\n(.*)/s);
- errorMessage.innerText = heading;
- if (body) {
- errorBody.innerText = body;
- } else {
- errorBody.innerText = '';
- }
-
- // Display component stack
- errorComponentStack.innerText = componentStack;
-
- // Display owner stack
- errorOwnerStack.innerText = ownerStack;
-
- // Display the call stack
- // Since we already displayed the message, strip it, and the first Error: line.
- errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1];
-
- // Display the cause, if available
- if (error.cause) {
- errorCauseMessage.innerText = error.cause.message;
- errorCauseStack.innerText = error.cause.stack;
- errorCause.classList.remove('hidden');
- } else {
- errorCause.classList.add('hidden');
+export function onCaughtErrorProd(error, errorInfo) {
+ if (error.message !== "Known error") {
+ reportError({ type: "Caught", error, errorInfo });
}
- // Display the close button, if dismissible
- if (dismissable) {
- errorNotDismissible.classList.add('hidden');
- errorClose.classList.remove("hidden");
- } else {
- errorNotDismissible.classList.remove('hidden');
- errorClose.classList.add("hidden");
- }
-
- // Show the dialog
- errorDialog.classList.remove("hidden");
-}
-
-export function reportCaughtError({error, cause, componentStack, ownerStack}) {
- reportError({ title: "Caught Error", error, componentStack, ownerStack, dismissable: true});
}
-export function reportUncaughtError({error, cause, componentStack, ownerStack}) {
- reportError({ title: "Uncaught Error", error, componentStack, ownerStack, dismissable: false });
+export function onUncaughtErrorProd(error, errorInfo) {
+ reportError({ type: "Uncaught", error, errorInfo });
}
-export function reportRecoverableError({error, cause, componentStack, ownerStack}) {
- reportError({ title: "Recoverable Error", error, componentStack, ownerStack, dismissable: true });
+export function onRecoverableErrorProd(error, errorInfo) {
+ reportError({ type: "Recoverable", error, errorInfo });
}
```
```js src/index.js active
-// captureOwnerStack is only available in react@canary.
-import { captureOwnerStack } from 'react';
import { createRoot } from "react-dom/client";
import App from "./App.js";
-import {reportUncaughtError} from "./reportError";
-import "./styles.css";
+import {
+ onCaughtErrorProd,
+ onRecoverableErrorProd,
+ onUncaughtErrorProd,
+} from "./reportError";
const container = document.getElementById("root");
const root = createRoot(container, {
- onUncaughtError: (error, errorInfo) => {
- if (error.message !== 'Known error') {
- reportUncaughtError({
- error,
- componentStack: errorInfo.componentStack,
- ownerStack: captureOwnerStack()
- });
- }
- }
+ // Keep in mind to remove these options in development to leverage
+ // React's default handlers or implement your own overlay for development.
+ // The handlers are only specfied unconditionally here for demonstration purposes.
+ onCaughtError: onCaughtErrorProd,
+ onRecoverableError: onRecoverableErrorProd,
+ onUncaughtError: onUncaughtErrorProd,
});
root.render();
```
```js src/App.js
-import { useState } from 'react';
+import { Component, useState } from "react";
-function Component() {
- const [throwError, setThrowError] = useState(false);
-
- if (throwError) {
- foo.bar = 'baz';
- }
-
- return (
- <>
- This error shows the error dialog:
-
- >
- );
+function Boom() {
+ foo.bar = "baz";
}
-export default function App() {
- return (
-
-
-
- );
-}
-```
+class ErrorBoundary extends Component {
+ state = { hasError: false };
-```json package.json hidden
-{
- "dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
- "react-scripts": "^5.0.0",
- "react-error-boundary": "4.0.3"
+ static getDerivedStateFromError(error) {
+ return { hasError: true };
}
-}
-```
-
-
-
-
-### Displaying Error Boundary errors {/*displaying-error-boundary-errors*/}
-By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option to handle errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary):
-
-```js [[1, 8, "onCaughtError"], [2, 8, "error", 1], [3, 8, "errorInfo"], [4, 12, "componentStack", 15], [5, 13, "captureOwnerStack()"]]
-// captureOwnerStack is only available in react@canary.
-import {captureOwnerStack} from 'react';
-import { createRoot } from "react-dom/client";
-import {reportCaughtError} from "./reportError";
-
-const container = document.getElementById("root");
-const root = createRoot(container, {
- onCaughtError: (error, errorInfo) => {
- if (error.message !== 'Known error') {
- reportCaughtError({
- error,
- componentStack: errorInfo.componentStack,
- ownerStack: captureOwnerStack()
- });
+ render() {
+ if (this.state.hasError) {
+ return
Something went wrong.
;
}
+ return this.props.children;
}
-});
-```
-
-The onCaughtError option is a function called with two arguments:
-
-1. The error that was caught by the boundary.
-2. An errorInfo object that contains the componentStack of the error.
-
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
-
-You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging:
-
-
-
-```html public/index.html hidden
-
-
-
- My app
-
-
-
-
- );
-}
-
-function Throw({error}) {
- if (error === "known") {
- throw new Error('Known error')
- } else {
- foo.bar = 'baz';
- }
-}
-```
-
-```json package.json hidden
-{
- "dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
- "react-scripts": "^5.0.0",
- "react-error-boundary": "4.0.3"
- }
-}
```
-### Displaying a dialog for recoverable errors {/*displaying-a-dialog-for-recoverable-errors*/}
-
-React may automatically render a component a second time to attempt to recover from an error thrown in render. If successful, React will log a recoverable error to the console to notify the developer. To override this behavior, you can provide the optional `onRecoverableError` root option:
-
-```js [[1, 8, "onRecoverableError"], [2, 8, "error", 1], [3, 11, "error.cause"], [4, 8, "errorInfo"], [5, 12, "componentStack", 15], [6, 13, "captureOwnerStack()"]]
-// captureOwnerStack is only available in react@canary.
-import {captureOwnerStack} from 'react'
-import { createRoot } from "react-dom/client";
-import {reportRecoverableError} from "./reportError";
-
-const container = document.getElementById("root");
-const root = createRoot(container, {
- onRecoverableError: (error, errorInfo) => {
- reportRecoverableError({
- error,
- cause: error.cause,
- componentStack: errorInfo.componentStack,
- ownerStack: captureOwnerStack(),
- });
- }
-});
-```
-
-The onRecoverableError option is a function called with two arguments:
-
-1. The error that React throws. Some errors may include the original cause as error.cause.
-2. An errorInfo object that contains the componentStack of the error.
-
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
-
-You can use the `onRecoverableError` root option to display error dialogs:
-
-
-
-```html public/index.html hidden
-
-
-
- My app
-
-
-
-
- );
-}
-
-function Throw({error}) {
- // Simulate an external value changing during concurrent render.
- errorThrown = true;
- foo.bar = 'baz';
-}
-```
-
-```json package.json hidden
-{
- "dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
- "react-scripts": "^5.0.0",
- "react-error-boundary": "4.0.3"
- },
- "main": "/index.js"
-}
-```
-
-
-
-
----
## Troubleshooting {/*troubleshooting*/}
### I've created a root, but nothing is displayed {/*ive-created-a-root-but-nothing-is-displayed*/}
diff --git a/src/content/reference/react-dom/client/hydrateRoot.md b/src/content/reference/react-dom/client/hydrateRoot.md
index 83d95a82cd8..887cab7d276 100644
--- a/src/content/reference/react-dom/client/hydrateRoot.md
+++ b/src/content/reference/react-dom/client/hydrateRoot.md
@@ -374,601 +374,124 @@ export default function App({counter}) {
It is uncommon to call [`root.render`](#root-render) on a hydrated root. Usually, you'll [update state](/reference/react/useState) inside one of the components instead.
-### Show a dialog for uncaught errors {/*show-a-dialog-for-uncaught-errors*/}
+### Error logging in production {/*error-logging-in-production*/}
-By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option:
+By default, React will log all errors to the console. To implement your own error reporting, you can provide the optional error handler root options `onUncaughtError`, `onCaughtError` and `onRecoverableError`:
-```js [[1, 9, "onUncaughtError"], [2, 9, "error", 1], [3, 9, "errorInfo"], [4, 13, "componentStack", 15], [5, 14, "captureOwnerStack()"]]
-// captureOwnerStack is only available in react@canary.
-import { captureOwnerStack } from 'react';
+```js [[1, 6, "onCaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack", 15]]
import { hydrateRoot } from "react-dom/client";
-import App from "./App";
-import {reportUncaughtError} from "./reportError";
+import { reportCaughtError } from "./reportError";
const container = document.getElementById("root");
-hydrateRoot(container, , {
- onUncaughtError: (error, errorInfo) => {
- if (error.message !== 'Known error') {
- reportUncaughtError({
+const root = hydrateRoot(container, {
+ onCaughtError: (error, errorInfo) => {
+ if (error.message !== "Known error") {
+ reportCaughtError({
error,
componentStack: errorInfo.componentStack,
- ownerStack: captureOwnerStack()
});
}
- }
+ },
});
```
-The onUncaughtError option is a function called with two arguments:
+The onCaughtError option is a function called with two arguments:
1. The error that was thrown.
2. An errorInfo object that contains the componentStack of the error.
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
-
-You can use the `onUncaughtError` root option to display error dialogs:
+Together with `onUncaughtError` and `onRecoverableError`, you can can implement your own error reporting system:
-```html public/index.html hidden
-
-
-
- My app
-
-
-
-
-
-
-
-
-
-
-
-
Component stack:
-
-
Owner stack:
-
-
Call stack:
-
-
-
Caused by:
-
-
-
-
-
This error is not dismissible.
-
-
-
This error shows the error dialog:
-
-
-```
-
-```css src/styles.css active
-label, button { display: block; margin-bottom: 20px; }
-html, body { min-height: 300px; }
-
-#error-dialog {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- background-color: white;
- padding: 15px;
- opacity: 0.9;
- text-wrap: wrap;
- overflow: scroll;
-}
-
-.text-red {
- color: red;
-}
-
-.-mb-20 {
- margin-bottom: -20px;
-}
-
-.mb-0 {
- margin-bottom: 0;
-}
-
-.mb-10 {
- margin-bottom: 10px;
-}
-
-pre {
- text-wrap: wrap;
-}
-
-pre.nowrap {
- text-wrap: nowrap;
-}
-
-.hidden {
- display: none;
+```js src/reportError.js
+function reportError({ type, error, errorInfo }) {
+ // The specific implementation is up to you.
+ // `console.error()` is only used for demonstration purposes.
+ console.error(type, error, "Component Stack: ");
+ console.error("Component Stack: ", errorInfo.componentStack);
}
-```
-```js src/reportError.js hidden
-function reportError({ title, error, componentStack, ownerStack, dismissable }) {
- const errorDialog = document.getElementById("error-dialog");
- const errorTitle = document.getElementById("error-title");
- const errorMessage = document.getElementById("error-message");
- const errorBody = document.getElementById("error-body");
- const errorComponentStack = document.getElementById("error-component-stack");
- const errorOwnerStack = document.getElementById("error-owner-stack");
- const errorStack = document.getElementById("error-stack");
- const errorClose = document.getElementById("error-close");
- const errorCause = document.getElementById("error-cause");
- const errorCauseMessage = document.getElementById("error-cause-message");
- const errorCauseStack = document.getElementById("error-cause-stack");
- const errorNotDismissible = document.getElementById("error-not-dismissible");
-
- // Set the title
- errorTitle.innerText = title;
-
- // Display error message and body
- const [heading, body] = error.message.split(/\n(.*)/s);
- errorMessage.innerText = heading;
- if (body) {
- errorBody.innerText = body;
- } else {
- errorBody.innerText = '';
+export function onCaughtErrorProd(error, errorInfo) {
+ if (error.message !== "Known error") {
+ reportError({ type: "Caught", error, errorInfo });
}
-
- // Display component stack
- errorComponentStack.innerText = componentStack;
-
- // Display owner stack
- errorOwnerStack.innerText = ownerStack;
-
- // Display the call stack
- // Since we already displayed the message, strip it, and the first Error: line.
- errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1];
-
- // Display the cause, if available
- if (error.cause) {
- errorCauseMessage.innerText = error.cause.message;
- errorCauseStack.innerText = error.cause.stack;
- errorCause.classList.remove('hidden');
- } else {
- errorCause.classList.add('hidden');
- }
- // Display the close button, if dismissible
- if (dismissable) {
- errorNotDismissible.classList.add('hidden');
- errorClose.classList.remove("hidden");
- } else {
- errorNotDismissible.classList.remove('hidden');
- errorClose.classList.add("hidden");
- }
-
- // Show the dialog
- errorDialog.classList.remove("hidden");
-}
-
-export function reportCaughtError({error, cause, componentStack, ownerStack}) {
- reportError({ title: "Caught Error", error, componentStack, ownerStack, dismissable: true});
}
-export function reportUncaughtError({error, cause, componentStack, ownerStack}) {
- reportError({ title: "Uncaught Error", error, componentStack, ownerStack, dismissable: false });
+export function onUncaughtErrorProd(error, errorInfo) {
+ reportError({ type: "Uncaught", error, errorInfo });
}
-export function reportRecoverableError({error, cause, componentStack, ownerStack}) {
- reportError({ title: "Recoverable Error", error, componentStack, ownerStack, ownerStack, dismissable: true });
+export function onRecoverableErrorProd(error, errorInfo) {
+ reportError({ type: "Recoverable", error, errorInfo });
}
```
```js src/index.js active
-// captureOwnerStack is only available in react@canary.
-import { captureOwnerStack } from 'react';
import { hydrateRoot } from "react-dom/client";
-import App from "./App";
-import {reportUncaughtError} from "./reportError";
-import "./styles.css";
-import {renderToString} from 'react-dom/server';
+import App from "./App.js";
+import {
+ onCaughtErrorProd,
+ onRecoverableErrorProd,
+ onUncaughtErrorProd,
+} from "./reportError";
const container = document.getElementById("root");
hydrateRoot(container, , {
- onUncaughtError: (error, errorInfo) => {
- if (error.message !== 'Known error') {
- reportUncaughtError({
- error,
- componentStack: errorInfo.componentStack,
- ownerStack: captureOwnerStack()
- });
- }
- }
+ // Keep in mind to remove these options in development to leverage
+ // React's default handlers or implement your own overlay for development.
+ // The handlers are only specfied unconditionally here for demonstration purposes.
+ onCaughtError: onCaughtErrorProd,
+ onRecoverableError: onRecoverableErrorProd,
+ onUncaughtError: onUncaughtErrorProd,
});
```
```js src/App.js
-import { useState } from 'react';
+import { Component, useState } from "react";
-function Component() {
- const [throwError, setThrowError] = useState(false);
-
- if (throwError) {
- foo.bar = 'baz';
- }
-
- return (
- <>
- This error shows the error dialog:
-
- >
- );
+function Boom() {
+ foo.bar = "baz";
}
-export default function App() {
- return (
-
-
-
- );
-}
-```
+class ErrorBoundary extends Component {
+ state = { hasError: false };
-```json package.json hidden
-{
- "dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
- "react-scripts": "^5.0.0",
- "react-error-boundary": "4.0.3"
+ static getDerivedStateFromError(error) {
+ return { hasError: true };
}
-}
-```
-
-
-
-
-### Displaying Error Boundary errors {/*displaying-error-boundary-errors*/}
-
-By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option for errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary):
-
-```js [[1, 9, "onCaughtError"], [2, 9, "error", 1], [3, 9, "errorInfo"], [4, 13, "componentStack", 15], [5, 14, "captureOwnerStack()"]]
-// captureOwnerStack is only available in react@canary.
-import {captureOwnerStack} from 'react';
-import { hydrateRoot } from "react-dom/client";
-import App from "./App.js";
-import {reportCaughtError} from "./reportError";
-const container = document.getElementById("root");
-hydrateRoot(container, , {
- onCaughtError: (error, errorInfo) => {
- if (error.message !== 'Known error') {
- reportCaughtError({
- error,
- componentStack: errorInfo.componentStack,
- ownerStack: captureOwnerStack()
- });
+ render() {
+ if (this.state.hasError) {
+ return
Something went wrong.
;
}
+ return this.props.children;
}
-});
-```
-
-The onCaughtError option is a function called with two arguments:
-
-1. The error that was caught by the boundary.
-2. An errorInfo object that contains the componentStack of the error.
-
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
-
-You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging:
-
-
-
-```html public/index.html hidden
-
-
-
- My app
-
-
-
-
-
-
-
-
-
-
-
-
Component stack:
-
-
Owner stack:
-
-
Call stack:
-
-
-
Caused by:
-
-
-
-
-
This error is not dismissible.
-
-
-
This error will not show the error dialog:This error will show the error dialog:
- );
-}
-
-function Throw({error}) {
- if (error === "known") {
- throw new Error('Known error')
- } else {
- foo.bar = 'baz';
- }
-}
-```
-
-```json package.json hidden
-{
- "dependencies": {
- "react": "experimental",
- "react-dom": "experimental",
- "react-scripts": "^5.0.0",
- "react-error-boundary": "4.0.3"
- }
-}
```
-
-
-### Show a dialog for recoverable hydration mismatch errors {/*show-a-dialog-for-recoverable-hydration-mismatch-errors*/}
-
-When React encounters a hydration mismatch, it will automatically attempt to recover by rendering on the client. By default, React will log hydration mismatch errors to `console.error`. To override this behavior, you can provide the optional `onRecoverableError` root option:
-
-```js [[1, 9, "onRecoverableError"], [2, 9, "error", 1], [3, 12, "error.cause", 1], [4, 9, "errorInfo"], [5, 13, "componentStack", 15], [6, 14, "captureOwnerStack()"]]
-// captureOwnerStack is only available in react@canary.
-import {captureOwnerStack} from 'react'
-import { hydrateRoot } from "react-dom/client";
-import App from "./App.js";
-import {reportRecoverableError} from "./reportError";
-
-const container = document.getElementById("root");
-const root = hydrateRoot(container, , {
- onRecoverableError: (error, errorInfo) => {
- reportRecoverableError({
- error,
- cause: error.cause,
- componentStack: errorInfo.componentStack,
- ownerStack: captureOwnerStack()
- });
- }
-});
-```
-
-The onRecoverableError option is a function called with two arguments:
-
-1. The error React throws. Some errors may include the original cause as error.cause.
-2. An errorInfo object that contains the componentStack of the error.
-
- With [`captureOwnerStack`](/reference/react/captureOwnerStack) you can include the Owner Stack during development.
-
-You can use the `onRecoverableError` root option to display error dialogs for hydration mismatches:
-
-
-
```html public/index.html hidden
@@ -977,225 +500,12 @@ You can use the `onRecoverableError` root option to display error dialogs for hy
-