@samitouri / QOS-React-2 / commits / 2237efcef9

[Fresh] Track unrecoverable errors (#16183)

* [Fresh] Track unrecoverable errors * Only initial errors set the flag

Dan Abramov committed Jul 23, 2019 at 16:11 UTC 2237efcef9a8e63c1b3f1bd61481cb215d20986a
2 files changed +46
packages/react-refresh/src/ReactFreshRuntime.js
+18
@@ -65,6 +65,7 @@ let findHostInstancesForRefresh: null | FindHostInstancesForRefresh = null;
65 let mountedRoots: Set<FiberRoot> = new Set();
66 // If a root captures an error, we add its element to this Map so we can retry on edit.
67 let failedRoots: Map<FiberRoot, ReactNodeList> = new Map();
68 +let didSomeRootFailOnMount = false;
69
70 function computeFullKey(signature: Signature): string {
71 if (signature.fullKey !== null) {
@@ -459,6 +460,19 @@ export function injectIntoGlobalHook(globalObject: any): void {
460 // Remember what was rendered so we can restore it.
461 failedRoots.set(root, alternate.memoizedState.element);
462 }
463 + } else if (!wasMounted && !isMounted) {
464 + if (didError && !failedRoots.has(root)) {
465 + // The root had an error during the initial mount.
466 + // We can't read its last element from the memoized state
467 + // because there was no previously committed alternate.
468 + // Ideally, it would be nice if we had a way to extract
469 + // the last attempted rendered element, but accessing the update queue
470 + // would tie this package too closely to the reconciler version.
471 + // So instead, we just set a flag.
472 + // TODO: Maybe we could fix this as the same time as when we fix
473 + // DevTools to not depend on `alternate.memoizedState.element`.
474 + didSomeRootFailOnMount = true;
475 + }
476 }
477 } else {
478 // Mount a new root.
@@ -474,6 +488,10 @@ export function injectIntoGlobalHook(globalObject: any): void {
488 }
489 }
490
491 +export function hasUnrecoverableErrors() {
492 + return didSomeRootFailOnMount;
493 +}
494 +
495 // Exposed for testing.
496 export function _getMountedRootCount() {
497 if (__DEV__) {
packages/react-refresh/src/__tests__/ReactFresh-test.js
+28
@@ -2712,8 +2712,28 @@ describe('ReactFresh', () => {
2712 }
2713 });
2714
2715 + // TODO: we can make this recoverable in the future
2716 + // if we add a way to track the last attempted element.
2717 + it('records an unrecoverable error if a root fails on mount', () => {
2718 + if (__DEV__) {
2719 + expect(ReactFreshRuntime.hasUnrecoverableErrors()).toBe(false);
2720 + expect(() => {
2721 + render(() => {
2722 + function Hello() {
2723 + throw new Error('No');
2724 + }
2725 + $RefreshReg$(Hello, 'Hello');
2726 +
2727 + return Hello;
2728 + });
2729 + }).toThrow('No');
2730 + expect(ReactFreshRuntime.hasUnrecoverableErrors()).toBe(true);
2731 + }
2732 + });
2733 +
2734 it('remounts a failed root on update', () => {
2735 if (__DEV__) {
2736 + expect(ReactFreshRuntime.hasUnrecoverableErrors()).toBe(false);
2737 render(() => {
2738 function Hello() {
2739 return <h1>Hi</h1>;
@@ -2723,6 +2743,7 @@ describe('ReactFresh', () => {
2743 return Hello;
2744 });
2745 expect(container.innerHTML).toBe('<h1>Hi</h1>');
2746 + expect(ReactFreshRuntime.hasUnrecoverableErrors()).toBe(false);
2747
2748 // Perform a hot update that fails.
2749 // This removes the root.
@@ -2735,6 +2756,7 @@ describe('ReactFresh', () => {
2756 });
2757 }).toThrow('No');
2758 expect(container.innerHTML).toBe('');
2759 + expect(ReactFreshRuntime.hasUnrecoverableErrors()).toBe(false);
2760
2761 // A bad retry
2762 expect(() => {
@@ -2746,6 +2768,7 @@ describe('ReactFresh', () => {
2768 });
2769 }).toThrow('Not yet');
2770 expect(container.innerHTML).toBe('');
2771 + expect(ReactFreshRuntime.hasUnrecoverableErrors()).toBe(false);
2772
2773 // Perform a hot update that fixes the error.
2774 patch(() => {
@@ -2756,6 +2779,7 @@ describe('ReactFresh', () => {
2779 });
2780 // This should remount the root.
2781 expect(container.innerHTML).toBe('<h1>Fixed!</h1>');
2782 + expect(ReactFreshRuntime.hasUnrecoverableErrors()).toBe(false);
2783
2784 // Verify next hot reload doesn't remount anything.
2785 let helloNode = container.firstChild;
@@ -2767,6 +2791,7 @@ describe('ReactFresh', () => {
2791 });
2792 expect(container.firstChild).toBe(helloNode);
2793 expect(helloNode.textContent).toBe('Nice.');
2794 + expect(ReactFreshRuntime.hasUnrecoverableErrors()).toBe(false);
2795
2796 // Break again.
2797 expect(() => {
@@ -2778,6 +2803,7 @@ describe('ReactFresh', () => {
2803 });
2804 }).toThrow('Oops');
2805 expect(container.innerHTML).toBe('');
2806 + expect(ReactFreshRuntime.hasUnrecoverableErrors()).toBe(false);
2807
2808 // Perform a hot update that fixes the error.
2809 patch(() => {
@@ -2788,6 +2814,7 @@ describe('ReactFresh', () => {
2814 });
2815 // This should remount the root.
2816 expect(container.innerHTML).toBe('<h1>At last.</h1>');
2817 + expect(ReactFreshRuntime.hasUnrecoverableErrors()).toBe(false);
2818
2819 // Check we don't attempt to reverse an intentional unmount.
2820 ReactDOM.unmountComponentAtNode(container);
@@ -2799,6 +2826,7 @@ describe('ReactFresh', () => {
2826 $RefreshReg$(Hello, 'Hello');
2827 });
2828 expect(container.innerHTML).toBe('');
2829 + expect(ReactFreshRuntime.hasUnrecoverableErrors()).toBe(false);
2830 }
2831 });
2832