[Fizz] Move digest from errorInfo to Error instance (#25313)
* suspense boundary error digest to Error instance and deprecate digest from errorInfo for onRecoverableError * fix closure escape
Josh Story committed
Sep 22, 2022 at 12:40 UTC
112d0498c8e05100f0a1fa0c4c0cc11534ae5353
5 files changed
+108
-7
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+42
-1
@@ -92,7 +92,7 @@ describe('ReactDOMFizzServer', () => {
92
function expectErrors(errorsArr, toBeDevArr, toBeProdArr) {
93
const mappedErrows = errorsArr.map(({error, errorInfo}) => {
94
const stack = errorInfo && errorInfo.componentStack;
95
- const digest = errorInfo && errorInfo.digest;
95
+ const digest = error.digest;
96
if (stack) {
97
return [error.message, digest, normalizeCodeLocInfo(stack)];
98
} else if (digest) {
@@ -3230,6 +3230,47 @@ describe('ReactDOMFizzServer', () => {
3230
);
3231
});
3232
3233
+ it('warns in dev if you access digest from errorInfo in onRecoverableError', async () => {
3234
+ await act(async () => {
3235
+ const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3236
+ <div>
3237
+ <Suspense fallback={'loading...'}>
3238
+ <AsyncText text={'hello'} />
3239
+ </Suspense>
3240
+ </div>,
3241
+ {
3242
+ onError(error) {
3243
+ return 'a digest';
3244
+ },
3245
+ },
3246
+ );
3247
+ rejectText('hello');
3248
+ pipe(writable);
3249
+ });
3250
+ expect(getVisibleChildren(container)).toEqual(<div>loading...</div>);
3251
+
3252
+ ReactDOMClient.hydrateRoot(
3253
+ container,
3254
+ <div>
3255
+ <Suspense fallback={'loading...'}>hello</Suspense>
3256
+ </div>,
3257
+ {
3258
+ onRecoverableError(error, errorInfo) {
3259
+ expect(() => {
3260
+ expect(error.digest).toBe('a digest');
3261
+ expect(errorInfo.digest).toBe('a digest');
3262
+ }).toErrorDev(
3263
+ 'Warning: You are accessing "digest" from the errorInfo object passed to onRecoverableError.' +
3264
+ ' This property is deprecated and will be removed in a future version of React.' +
3265
+ ' To access the digest of an Error look for this property on the Error instance itself.',
3266
+ {withoutStack: true},
3267
+ );
3268
+ },
3269
+ },
3270
+ );
3271
+ expect(Scheduler).toFlushWithoutYielding();
3272
+ });
3273
+
3274
describe('error escaping', () => {
3275
it('escapes error hash, message, and component stack values in directly flushed errors (html escaping)', async () => {
3276
window.__outlet = {};
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+1
@@ -2746,6 +2746,7 @@ function updateDehydratedSuspenseComponent(
2746
'client rendering.',
2747
);
2748
}
2749
+ (error: any).digest = digest;
2750
const capturedValue = createCapturedValue(error, digest, stack);
2751
return retrySuspenseComponentWithoutHydrating(
2752
current,
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+1
@@ -2746,6 +2746,7 @@ function updateDehydratedSuspenseComponent(
2746
'client rendering.',
2747
);
2748
}
2749
+ (error: any).digest = digest;
2750
const capturedValue = createCapturedValue(error, digest, stack);
2751
return retrySuspenseComponentWithoutHydrating(
2752
current,
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+32
-3
@@ -2596,9 +2596,11 @@ function commitRootImpl(
2596
const onRecoverableError = root.onRecoverableError;
2597
for (let i = 0; i < recoverableErrors.length; i++) {
2598
const recoverableError = recoverableErrors[i];
2599
- const componentStack = recoverableError.stack;
2600
- const digest = recoverableError.digest;
2601
- onRecoverableError(recoverableError.value, {componentStack, digest});
2599
+ const errorInfo = makeErrorInfo(
2600
+ recoverableError.digest,
2601
+ recoverableError.stack,
2602
+ );
2603
+ onRecoverableError(recoverableError.value, errorInfo);
2604
}
2605
}
2606
@@ -2689,6 +2691,33 @@ function commitRootImpl(
2691
return null;
2692
}
2693
2694
+function makeErrorInfo(digest: ?string, componentStack: ?string) {
2695
+ if (__DEV__) {
2696
+ const errorInfo = {
2697
+ componentStack,
2698
+ digest,
2699
+ };
2700
+ Object.defineProperty(errorInfo, 'digest', {
2701
+ configurable: false,
2702
+ enumerable: true,
2703
+ get() {
2704
+ console.error(
2705
+ 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' +
2706
+ ' This property is deprecated and will be removed in a future version of React.' +
2707
+ ' To access the digest of an Error look for this property on the Error instance itself.',
2708
+ );
2709
+ return digest;
2710
+ },
2711
+ });
2712
+ return errorInfo;
2713
+ } else {
2714
+ return {
2715
+ digest,
2716
+ componentStack,
2717
+ };
2718
+ }
2719
+}
2720
+
2721
function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) {
2722
if (enableCache) {
2723
const pooledCacheLanes = (root.pooledCacheLanes &= remainingLanes);
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+32
-3
@@ -2596,9 +2596,11 @@ function commitRootImpl(
2596
const onRecoverableError = root.onRecoverableError;
2597
for (let i = 0; i < recoverableErrors.length; i++) {
2598
const recoverableError = recoverableErrors[i];
2599
- const componentStack = recoverableError.stack;
2600
- const digest = recoverableError.digest;
2601
- onRecoverableError(recoverableError.value, {componentStack, digest});
2599
+ const errorInfo = makeErrorInfo(
2600
+ recoverableError.digest,
2601
+ recoverableError.stack,
2602
+ );
2603
+ onRecoverableError(recoverableError.value, errorInfo);
2604
}
2605
}
2606
@@ -2689,6 +2691,33 @@ function commitRootImpl(
2691
return null;
2692
}
2693
2694
+function makeErrorInfo(digest: ?string, componentStack: ?string) {
2695
+ if (__DEV__) {
2696
+ const errorInfo = {
2697
+ componentStack,
2698
+ digest,
2699
+ };
2700
+ Object.defineProperty(errorInfo, 'digest', {
2701
+ configurable: false,
2702
+ enumerable: true,
2703
+ get() {
2704
+ console.error(
2705
+ 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' +
2706
+ ' This property is deprecated and will be removed in a future version of React.' +
2707
+ ' To access the digest of an Error look for this property on the Error instance itself.',
2708
+ );
2709
+ return digest;
2710
+ },
2711
+ });
2712
+ return errorInfo;
2713
+ } else {
2714
+ return {
2715
+ digest,
2716
+ componentStack,
2717
+ };
2718
+ }
2719
+}
2720
+
2721
function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) {
2722
if (enableCache) {
2723
const pooledCacheLanes = (root.pooledCacheLanes &= remainingLanes);