@samitouri / QOS-React / commits / 0c7189d923

Fix resolution of outer props with React.memo() (#14312)

* Add failing test for defaultProps between lazy() and memo() * Add another regression test for defaultProps resolution order * Resolve outer props for MemoComponent

Dan Abramov committed Nov 22, 2018 at 19:40 UTC 0c7189d92370717ce8c48cf4846b3a101056e772
2 files changed +137 -2
packages/react-reconciler/src/ReactFiberBeginWork.js
+9 -2
@@ -250,7 +250,12 @@ function updateMemoComponent(
250 ): null | Fiber {
251 if (current === null) {
252 let type = Component.type;
253 - if (isSimpleFunctionComponent(type) && Component.compare === null) {
253 + if (
254 + isSimpleFunctionComponent(type) &&
255 + Component.compare === null &&
256 + // SimpleMemoComponent codepath doesn't resolve outer props either.
257 + Component.defaultProps === undefined
258 + ) {
259 // If this is a plain function component without default props,
260 // and with only the default shallow comparison, we upgrade it
261 // to a SimpleMemoComponent to allow fast path updates.
@@ -1739,7 +1744,9 @@ function beginWork(
1744 case MemoComponent: {
1745 const type = workInProgress.type;
1746 const unresolvedProps = workInProgress.pendingProps;
1742 - const resolvedProps = resolveDefaultProps(type.type, unresolvedProps);
1747 + // Resolve outer props first, then resolve inner props.
1748 + let resolvedProps = resolveDefaultProps(type, unresolvedProps);
1749 + resolvedProps = resolveDefaultProps(type.type, resolvedProps);
1750 return updateMemoComponent(
1751 current,
1752 workInProgress,
packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+128
@@ -533,4 +533,132 @@ describe('ReactLazy', () => {
533 expect(root).toMatchRenderedOutput('FooBar');
534 expect(ref.current).not.toBe(null);
535 });
536 +
537 + // Regression test for #14310
538 + it('supports defaultProps defined on the memo() return value', async () => {
539 + const Add = React.memo(props => {
540 + return props.inner + props.outer;
541 + });
542 + Add.defaultProps = {
543 + inner: 2,
544 + };
545 + const LazyAdd = lazy(() => fakeImport(Add));
546 + const root = ReactTestRenderer.create(
547 + <Suspense fallback={<Text text="Loading..." />}>
548 + <LazyAdd outer={2} />
549 + </Suspense>,
550 + {
551 + unstable_isConcurrent: true,
552 + },
553 + );
554 + expect(root).toFlushAndYield(['Loading...']);
555 + expect(root).toMatchRenderedOutput(null);
556 +
557 + // Mount
558 + await Promise.resolve();
559 + root.unstable_flushAll();
560 + expect(root).toMatchRenderedOutput('4');
561 +
562 + // Update (shallowly equal)
563 + root.update(
564 + <Suspense fallback={<Text text="Loading..." />}>
565 + <LazyAdd outer={2} />
566 + </Suspense>,
567 + );
568 + root.unstable_flushAll();
569 + expect(root).toMatchRenderedOutput('4');
570 +
571 + // Update
572 + root.update(
573 + <Suspense fallback={<Text text="Loading..." />}>
574 + <LazyAdd outer={3} />
575 + </Suspense>,
576 + );
577 + root.unstable_flushAll();
578 + expect(root).toMatchRenderedOutput('5');
579 +
580 + // Update (shallowly equal)
581 + root.update(
582 + <Suspense fallback={<Text text="Loading..." />}>
583 + <LazyAdd outer={3} />
584 + </Suspense>,
585 + );
586 + root.unstable_flushAll();
587 + expect(root).toMatchRenderedOutput('5');
588 +
589 + // Update (explicit props)
590 + root.update(
591 + <Suspense fallback={<Text text="Loading..." />}>
592 + <LazyAdd outer={1} inner={1} />
593 + </Suspense>,
594 + );
595 + root.unstable_flushAll();
596 + expect(root).toMatchRenderedOutput('2');
597 +
598 + // Update (explicit props, shallowly equal)
599 + root.update(
600 + <Suspense fallback={<Text text="Loading..." />}>
601 + <LazyAdd outer={1} inner={1} />
602 + </Suspense>,
603 + );
604 + root.unstable_flushAll();
605 + expect(root).toMatchRenderedOutput('2');
606 +
607 + // Update
608 + root.update(
609 + <Suspense fallback={<Text text="Loading..." />}>
610 + <LazyAdd outer={1} />
611 + </Suspense>,
612 + );
613 + root.unstable_flushAll();
614 + expect(root).toMatchRenderedOutput('3');
615 + });
616 +
617 + it('merges defaultProps in the correct order', async () => {
618 + let Add = React.memo(props => {
619 + return props.inner + props.outer;
620 + });
621 + Add.defaultProps = {
622 + inner: 100,
623 + };
624 + Add = React.memo(Add);
625 + Add.defaultProps = {
626 + inner: 2,
627 + outer: 0,
628 + };
629 + const LazyAdd = lazy(() => fakeImport(Add));
630 + const root = ReactTestRenderer.create(
631 + <Suspense fallback={<Text text="Loading..." />}>
632 + <LazyAdd outer={2} />
633 + </Suspense>,
634 + {
635 + unstable_isConcurrent: true,
636 + },
637 + );
638 + expect(root).toFlushAndYield(['Loading...']);
639 + expect(root).toMatchRenderedOutput(null);
640 +
641 + // Mount
642 + await Promise.resolve();
643 + root.unstable_flushAll();
644 + expect(root).toMatchRenderedOutput('4');
645 +
646 + // Update
647 + root.update(
648 + <Suspense fallback={<Text text="Loading..." />}>
649 + <LazyAdd outer={3} />
650 + </Suspense>,
651 + );
652 + root.unstable_flushAll();
653 + expect(root).toMatchRenderedOutput('5');
654 +
655 + // Update
656 + root.update(
657 + <Suspense fallback={<Text text="Loading..." />}>
658 + <LazyAdd />
659 + </Suspense>,
660 + );
661 + root.unstable_flushAll();
662 + expect(root).toMatchRenderedOutput('2');
663 + });
664 });