From 2caef37842e38c84a3e44f0827e3ab2a1d8cef21 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Thu, 22 Sep 2022 09:25:37 -0700 Subject: [PATCH 1/2] suspense boundary error digest to Error instance and deprecate digest from errorInfo for onRecoverableError --- .../src/__tests__/ReactDOMFizzServer-test.js | 42 ++++++++++++++++++- .../src/ReactFiberBeginWork.new.js | 1 + .../src/ReactFiberBeginWork.old.js | 1 + .../src/ReactFiberWorkLoop.new.js | 18 +++++++- .../src/ReactFiberWorkLoop.old.js | 18 +++++++- 5 files changed, 77 insertions(+), 3 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 4d8e677e03739..0726209a2a3b1 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -92,7 +92,7 @@ describe('ReactDOMFizzServer', () => { function expectErrors(errorsArr, toBeDevArr, toBeProdArr) { const mappedErrows = errorsArr.map(({error, errorInfo}) => { const stack = errorInfo && errorInfo.componentStack; - const digest = errorInfo && errorInfo.digest; + const digest = error.digest; if (stack) { return [error.message, digest, normalizeCodeLocInfo(stack)]; } else if (digest) { @@ -3230,6 +3230,46 @@ describe('ReactDOMFizzServer', () => { ); }); + it('warns in dev if you access digest from errorInfo in onRecoverableError', async () => { + await act(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream( +
+ + + +
, + { + onError(error) { + return 'a digest'; + }, + }, + ); + rejectText('hello'); + pipe(writable); + }); + expect(getVisibleChildren(container)).toEqual(
loading...
); + + ReactDOMClient.hydrateRoot( + container, +
+ hello +
, + { + onRecoverableError(error, errorInfo) { + expect(() => { + expect(errorInfo.digest).toBe('a digest'); + }).toErrorDev( + 'Warning: You are accessing "digest" from the errorInfo object passed to onRecoverableError.' + + ' This property is deprecated and will be removed in a future version of React.' + + ' To access the digest of an Error look for this property on the Error instance itself.', + {withoutStack: true}, + ); + }, + }, + ); + expect(Scheduler).toFlushWithoutYielding(); + }); + describe('error escaping', () => { it('escapes error hash, message, and component stack values in directly flushed errors (html escaping)', async () => { window.__outlet = {}; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index 913a054be2dd2..c18bed4d808d3 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -2746,6 +2746,7 @@ function updateDehydratedSuspenseComponent( 'client rendering.', ); } + (error: any).digest = digest; const capturedValue = createCapturedValue(error, digest, stack); return retrySuspenseComponentWithoutHydrating( current, diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 382e9075716af..7b1f5b9c21b51 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -2746,6 +2746,7 @@ function updateDehydratedSuspenseComponent( 'client rendering.', ); } + (error: any).digest = digest; const capturedValue = createCapturedValue(error, digest, stack); return retrySuspenseComponentWithoutHydrating( current, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 0e4564538efba..74a4e8288002e 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -2598,7 +2598,23 @@ function commitRootImpl( const recoverableError = recoverableErrors[i]; const componentStack = recoverableError.stack; const digest = recoverableError.digest; - onRecoverableError(recoverableError.value, {componentStack, digest}); + let errorInfo; + if (__DEV__) { + errorInfo = { + componentStack, + get digest() { + console.error( + 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' + + ' This property is deprecated and will be removed in a future version of React.' + + ' To access the digest of an Error look for this property on the Error instance itself.', + ); + return digest; + }, + }; + } else { + errorInfo = {componentStack, digest}; + } + onRecoverableError(recoverableError.value, errorInfo); } } diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 496d3f91d5724..76661bd0c69d6 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -2598,7 +2598,23 @@ function commitRootImpl( const recoverableError = recoverableErrors[i]; const componentStack = recoverableError.stack; const digest = recoverableError.digest; - onRecoverableError(recoverableError.value, {componentStack, digest}); + let errorInfo; + if (__DEV__) { + errorInfo = { + componentStack, + get digest() { + console.error( + 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' + + ' This property is deprecated and will be removed in a future version of React.' + + ' To access the digest of an Error look for this property on the Error instance itself.', + ); + return digest; + }, + }; + } else { + errorInfo = {componentStack, digest}; + } + onRecoverableError(recoverableError.value, errorInfo); } } From a2f917dff36cbe26f916577b1538f89f4addf15a Mon Sep 17 00:00:00 2001 From: Josh Story Date: Thu, 22 Sep 2022 12:24:49 -0700 Subject: [PATCH 2/2] fix closure escape --- .../src/__tests__/ReactDOMFizzServer-test.js | 1 + .../src/ReactFiberWorkLoop.new.js | 49 ++++++++++++------- .../src/ReactFiberWorkLoop.old.js | 49 ++++++++++++------- 3 files changed, 63 insertions(+), 36 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 0726209a2a3b1..225d3657e0469 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -3257,6 +3257,7 @@ describe('ReactDOMFizzServer', () => { { onRecoverableError(error, errorInfo) { expect(() => { + expect(error.digest).toBe('a digest'); expect(errorInfo.digest).toBe('a digest'); }).toErrorDev( 'Warning: You are accessing "digest" from the errorInfo object passed to onRecoverableError.' + diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 74a4e8288002e..6d244630867b2 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -2596,24 +2596,10 @@ function commitRootImpl( const onRecoverableError = root.onRecoverableError; for (let i = 0; i < recoverableErrors.length; i++) { const recoverableError = recoverableErrors[i]; - const componentStack = recoverableError.stack; - const digest = recoverableError.digest; - let errorInfo; - if (__DEV__) { - errorInfo = { - componentStack, - get digest() { - console.error( - 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' + - ' This property is deprecated and will be removed in a future version of React.' + - ' To access the digest of an Error look for this property on the Error instance itself.', - ); - return digest; - }, - }; - } else { - errorInfo = {componentStack, digest}; - } + const errorInfo = makeErrorInfo( + recoverableError.digest, + recoverableError.stack, + ); onRecoverableError(recoverableError.value, errorInfo); } } @@ -2705,6 +2691,33 @@ function commitRootImpl( return null; } +function makeErrorInfo(digest: ?string, componentStack: ?string) { + if (__DEV__) { + const errorInfo = { + componentStack, + digest, + }; + Object.defineProperty(errorInfo, 'digest', { + configurable: false, + enumerable: true, + get() { + console.error( + 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' + + ' This property is deprecated and will be removed in a future version of React.' + + ' To access the digest of an Error look for this property on the Error instance itself.', + ); + return digest; + }, + }); + return errorInfo; + } else { + return { + digest, + componentStack, + }; + } +} + function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) { if (enableCache) { const pooledCacheLanes = (root.pooledCacheLanes &= remainingLanes); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 76661bd0c69d6..36799208dc69b 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -2596,24 +2596,10 @@ function commitRootImpl( const onRecoverableError = root.onRecoverableError; for (let i = 0; i < recoverableErrors.length; i++) { const recoverableError = recoverableErrors[i]; - const componentStack = recoverableError.stack; - const digest = recoverableError.digest; - let errorInfo; - if (__DEV__) { - errorInfo = { - componentStack, - get digest() { - console.error( - 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' + - ' This property is deprecated and will be removed in a future version of React.' + - ' To access the digest of an Error look for this property on the Error instance itself.', - ); - return digest; - }, - }; - } else { - errorInfo = {componentStack, digest}; - } + const errorInfo = makeErrorInfo( + recoverableError.digest, + recoverableError.stack, + ); onRecoverableError(recoverableError.value, errorInfo); } } @@ -2705,6 +2691,33 @@ function commitRootImpl( return null; } +function makeErrorInfo(digest: ?string, componentStack: ?string) { + if (__DEV__) { + const errorInfo = { + componentStack, + digest, + }; + Object.defineProperty(errorInfo, 'digest', { + configurable: false, + enumerable: true, + get() { + console.error( + 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' + + ' This property is deprecated and will be removed in a future version of React.' + + ' To access the digest of an Error look for this property on the Error instance itself.', + ); + return digest; + }, + }); + return errorInfo; + } else { + return { + digest, + componentStack, + }; + } +} + function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) { if (enableCache) { const pooledCacheLanes = (root.pooledCacheLanes &= remainingLanes);