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
});