@samitouri / QOS-React / commits / 8e5f12ca6c

Fixes bug when initial mount of a host component is `hidden` (#12294)

`oldProps` was null. This went uncaught by the unit tests because ReactNoop did not use `oldProps` in either `prepareUpdate` or `completeUpdate`. I added some invariants so we don't regress in the future.

Andrew Clark committed Feb 26, 2018 at 17:50 UTC 8e5f12ca6c18fb48ea71cba6bfa9bd7cf499ccb6
3 files changed +24 -3
packages/react-noop-renderer/src/ReactNoop.js
+9
@@ -130,6 +130,12 @@ let SharedHostConfig = {
130 oldProps: Props,
131 newProps: Props,
132 ): null | {} {
133 + if (oldProps === null) {
134 + throw new Error('Should have old props');
135 + }
136 + if (newProps === null) {
137 + throw new Error('Should have old props');
138 + }
139 return UPDATE_SIGNAL;
140 },
141
@@ -196,6 +202,9 @@ const NoopRenderer = ReactFiberReconciler({
202 oldProps: Props,
203 newProps: Props,
204 ): void {
205 + if (oldProps === null) {
206 + throw new Error('Should have old props');
207 + }
208 instance.prop = newProps.prop;
209 },
210
packages/react-reconciler/src/ReactFiberBeginWork.js
+13 -1
@@ -474,7 +474,18 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
474 // Normally we can bail out on props equality but if context has changed
475 // we don't do the bailout and we have to reuse existing props instead.
476 } else if (memoizedProps === nextProps) {
477 - return bailoutOnAlreadyFinishedWork(current, workInProgress);
477 + const isHidden =
478 + workInProgress.mode & AsyncMode &&
479 + shouldDeprioritizeSubtree(type, nextProps);
480 + if (isHidden) {
481 + // Before bailing out, make sure we've deprioritized a hidden component.
482 + workInProgress.expirationTime = Never;
483 + }
484 + if (!isHidden || renderExpirationTime !== Never) {
485 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
486 + }
487 + // If we're rendering a hidden node at hidden priority, don't bailout. The
488 + // parent is complete, but the children may not be.
489 }
490
491 let nextChildren = nextProps.children;
@@ -503,6 +514,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
514 // Down-prioritize the children.
515 workInProgress.expirationTime = Never;
516 // Bailout and come back to this fiber later.
517 + workInProgress.memoizedProps = nextProps;
518 return null;
519 }
520
packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.internal.js
+2 -2
@@ -758,7 +758,7 @@ describe('ReactIncrementalSideEffects', () => {
758 // items. Including the set state since that is deprioritized.
759 // TODO: The cycles it takes to do this could be lowered with further
760 // optimizations.
761 - ReactNoop.flushDeferredPri(60 + 5);
761 + ReactNoop.flushDeferredPri(35);
762 expect(ReactNoop.getChildren()).toEqual([
763 div(
764 // Updated.
@@ -790,7 +790,7 @@ describe('ReactIncrementalSideEffects', () => {
790 ),
791 ]);
792
793 - expect(ops).toEqual(['Bar', 'Bar']);
793 + expect(ops).toEqual(['Bar', 'Bar', 'Bar']);
794 });
795 // TODO: Test that side-effects are not cut off when a work in progress node
796 // moves to "current" without flushing due to having lower priority. Does this