diff --git a/src/components/MDX/CodeBlock/CodeBlock.tsx b/src/components/MDX/CodeBlock/CodeBlock.tsx index 7eef0abe893..6f176bb9f37 100644 --- a/src/components/MDX/CodeBlock/CodeBlock.tsx +++ b/src/components/MDX/CodeBlock/CodeBlock.tsx @@ -336,6 +336,7 @@ function getInlineDecorators( line.step === 3, 'bg-green-40 border-green-40 text-green-60 dark:text-green-30': line.step === 4, + // TODO: Some codeblocks use up to 6 steps. } ), }) diff --git a/src/components/MDX/MDXComponents.tsx b/src/components/MDX/MDXComponents.tsx index 0e22c79213e..b668991af80 100644 --- a/src/components/MDX/MDXComponents.tsx +++ b/src/components/MDX/MDXComponents.tsx @@ -119,6 +119,20 @@ 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. + You can use the `onUncaughtError` root option to display error dialogs: @@ -394,8 +399,10 @@ You can use the `onUncaughtError` root option to display error dialogs:


   

-

This error occurred at:

+

Component stack:


+  

Owner stack:

+

   

Call stack:


   
@@ -465,12 +472,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"); @@ -493,6 +501,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]; @@ -518,20 +529,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 {reportUncaughtError} from "./reportError"; @@ -543,7 +556,8 @@ const root = createRoot(container, { if (error.message !== 'Known error') { reportUncaughtError({ error, - componentStack: errorInfo.componentStack + componentStack: errorInfo.componentStack, + ownerStack: captureOwnerStack() }); } } @@ -554,7 +568,7 @@ root.render(); ```js src/App.js import { useState } from 'react'; -export default function App() { +function Component() { const [throwError, setThrowError] = useState(false); if (throwError) { @@ -562,16 +576,35 @@ export default function App() { } return ( -
+ <> 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"], [4, 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..79923ddf222 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 occurred at:

+

Component stack:


+  

Owner stack:

+

   

Call stack:


   
@@ -499,12 +505,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"); @@ -527,6 +534,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]; @@ -552,20 +562,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, 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 {reportUncaughtError} from "./reportError"; @@ -573,12 +585,13 @@ import "./styles.css"; import {renderToString} from 'react-dom/server'; const container = document.getElementById("root"); -const root = hydrateRoot(container, , { +hydrateRoot(container, , { onUncaughtError: (error, errorInfo) => { if (error.message !== 'Known error') { reportUncaughtError({ error, - componentStack: errorInfo.componentStack + componentStack: errorInfo.componentStack, + ownerStack: captureOwnerStack() }); } } @@ -588,7 +601,7 @@ const root = hydrateRoot(container, , { ```js src/App.js import { useState } from 'react'; -export default function App() { +function Component() { const [throwError, setThrowError] = useState(false); if (throwError) { @@ -596,16 +609,35 @@ export default function App() { } return ( -
+ <> 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 16694b2117b..0afcceefdc1 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*/} +### Show an error dialog {/*show-an-error-dialog*/} + +Check out the following example to see how to use `captureOwnerStack` to improve an error dialog: + +- [createRoot usage: Show a dialog for uncaught errors +](/reference/react-dom/client/hydrateRoot#updating-a-hydrated-root-component) +- [createRoot usage: Displaying Error Boundary errors](/reference/react-dom/client/createRoot#displaying-error-boundary-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.