If an error is thrown and there's no nextUnitOfWork, it's fatal error
This can only be caused by a bug in the renderer, but we should handle it gracefully anyway. Added a TODO to change capturedErrors and failedBoundaries so that they are sets of instances (stateNodes) rather than fibers, to avoid having to check the alternate. (This outside the scope of this PR.)
Andrew Clark committed
Dec 28, 2016 at 20:51 UTC
3926a3566fc8c28758b9f816006ea2495c63c71d
1 file changed
+36
-32
src/renderers/shared/fiber/ReactFiberScheduler.js
+36
-32
@@ -705,39 +705,40 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
705
} catch (error) {
706
// We caught an error during either the begin or complete phases.
707
const failedWork = nextUnitOfWork;
708
- if (!failedWork) {
709
- throw new Error('Should have nextUnitOfWork.');
710
- }
711
-
712
- // Reset the priority context to its value before reconcilation.
713
- priorityContext = priorityContextBeforeReconciliation;
714
-
715
- // "Capture" the error by finding the nearest boundary. If there is no
716
- // error boundary, the nearest host container acts as one. If
717
- // captureError returns null, the error was intentionally ignored.
718
- const maybeBoundary = captureError(failedWork, error);
719
- if (maybeBoundary) {
720
- const boundary = maybeBoundary;
721
-
722
- // Complete the boundary as if it rendered null. This will unmount
723
- // the failed tree.
724
- beginFailedWork(boundary.alternate, boundary, priorityLevel);
725
-
726
- // The next unit of work is now the boundary that captured the error.
727
- // Conceptually, we're unwinding the stack. We need to unwind the
728
- // context stack, too, from the failed work to the boundary that
729
- // captured the error.
730
- // TODO: If we set the memoized props in beginWork instead of
731
- // completeWork, rather than unwind the stack, we can just restart
732
- // from the root. Can't do that until then because without memoized
733
- // props, the nodes higher up in the tree will rerender unnecessarily.
734
- if (failedWork) {
708
+ if (failedWork) {
709
+ // Reset the priority context to its value before reconcilation.
710
+ priorityContext = priorityContextBeforeReconciliation;
711
+
712
+ // "Capture" the error by finding the nearest boundary. If there is no
713
+ // error boundary, the nearest host container acts as one. If
714
+ // captureError returns null, the error was intentionally ignored.
715
+ const maybeBoundary = captureError(failedWork, error);
716
+ if (maybeBoundary) {
717
+ const boundary = maybeBoundary;
718
+
719
+ // Complete the boundary as if it rendered null. This will unmount
720
+ // the failed tree.
721
+ beginFailedWork(boundary.alternate, boundary, priorityLevel);
722
+
723
+ // The next unit of work is now the boundary that captured the error.
724
+ // Conceptually, we're unwinding the stack. We need to unwind the
725
+ // context stack, too, from the failed work to the boundary that
726
+ // captured the error.
727
+ // TODO: If we set the memoized props in beginWork instead of
728
+ // completeWork, rather than unwind the stack, we can just restart
729
+ // from the root. Can't do that until then because without memoized
730
+ // props, the nodes higher up in the tree will rerender unnecessarily.
731
unwindContexts(failedWork, boundary);
732
+ nextUnitOfWork = completeUnitOfWork(boundary);
733
}
737
- nextUnitOfWork = completeUnitOfWork(boundary);
734
+ // Continue performing work
735
+ continue;
736
+ } else if (!fatalError) {
737
+ // There is no current unit of work. This is a worst-case scenario
738
+ // and should only be possible if there's a bug in the renderer, e.g.
739
+ // inside resetAfterCommit.
740
+ fatalError = error;
741
}
739
- // Continue performing work
740
- continue;
742
} finally {
743
priorityContext = priorityContextBeforeReconciliation;
744
}
@@ -900,6 +901,8 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
901
}
902
903
function hasCapturedError(fiber : Fiber) : boolean {
904
+ // TODO: capturedErrors should store the boundary instance, to avoid needing
905
+ // to check the alternate.
906
return Boolean(
907
capturedErrors &&
908
(capturedErrors.has(fiber) || (fiber.alternate && capturedErrors.has(fiber.alternate)))
@@ -907,11 +910,12 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
910
}
911
912
function isFailedBoundary(fiber : Fiber) : boolean {
910
- const res = Boolean(
913
+ // TODO: failedBoundaries should store the boundary instance, to avoid
914
+ // needing to check the alternate.
915
+ return Boolean(
916
failedBoundaries &&
917
(failedBoundaries.has(fiber) || (fiber.alternate && failedBoundaries.has(fiber.alternate)))
918
);
914
- return res;
919
}
920
921
function commitErrorHandling(effectfulFiber : Fiber) {