8
8
*/
9
9
10
10
import React from 'react' ;
11
- import { isForwardRef } from 'react-is' ;
11
+ import { isForwardRef , isMemo , ForwardRef } from 'react-is' ;
12
12
import describeComponentFrame from 'shared/describeComponentFrame' ;
13
13
import getComponentName from 'shared/getComponentName' ;
14
14
import shallowEqual from 'shared/shallowEqual' ;
@@ -500,7 +500,8 @@ class ReactShallowRenderer {
500
500
element . type ,
501
501
) ;
502
502
invariant (
503
- isForwardRef ( element ) || typeof element . type === 'function' ,
503
+ isForwardRef ( element ) ||
504
+ ( typeof element . type === 'function' || isMemo ( element . type ) ) ,
504
505
'ReactShallowRenderer render(): Shallow rendering works only with custom ' +
505
506
'components, but the provided element type was `%s`.' ,
506
507
Array . isArray ( element . type )
@@ -514,22 +515,36 @@ class ReactShallowRenderer {
514
515
return ;
515
516
}
516
517
518
+ const elementType = isMemo(element.type) ? element.type.type : element.type;
519
+ const previousElement = this._element;
520
+
517
521
this._rendering = true;
518
522
this._element = element;
519
- this._context = getMaskedContext(element.type.contextTypes, context);
523
+ this._context = getMaskedContext(elementType.contextTypes, context);
524
+
525
+ // Inner memo component props aren't currently validated in createElement.
526
+ if (isMemo(element.type) && elementType . propTypes ) {
527
+ currentlyValidatingElement = element ;
528
+ checkPropTypes (
529
+ elementType . propTypes ,
530
+ element . props ,
531
+ 'prop' ,
532
+ getComponentName ( elementType ) ,
533
+ getStackAddendum ,
534
+ ) ;
535
+ }
520
536
521
537
if (this._instance) {
522
- this . _updateClassComponent ( element , this . _context ) ;
538
+ this . _updateClassComponent ( elementType , element , this . _context ) ;
523
539
} else {
524
- if ( shouldConstruct ( element . type ) ) {
525
- this . _instance = new element . type (
540
+ if ( shouldConstruct ( elementType ) ) {
541
+ this . _instance = new elementType (
526
542
element . props ,
527
543
this . _context ,
528
544
this . _updater ,
529
545
) ;
530
-
531
- if ( typeof element . type . getDerivedStateFromProps === 'function' ) {
532
- const partialState = element . type . getDerivedStateFromProps . call (
546
+ if ( typeof elementType . getDerivedStateFromProps === 'function' ) {
547
+ const partialState = elementType . getDerivedStateFromProps . call (
533
548
null ,
534
549
element . props ,
535
550
this . _instance . state ,
@@ -543,39 +558,59 @@ class ReactShallowRenderer {
543
558
}
544
559
}
545
560
546
- if ( element . type . hasOwnProperty ( ' contextTypes ' ) ) {
561
+ if ( elementType . contextTypes ) {
547
562
currentlyValidatingElement = element ;
548
-
549
563
checkPropTypes (
550
- element . type . contextTypes ,
564
+ elementType . contextTypes ,
551
565
this . _context ,
552
566
'context' ,
553
- getName ( element . type , this . _instance ) ,
567
+ getName ( elementType , this . _instance ) ,
554
568
getStackAddendum ,
555
569
) ;
556
570
557
571
currentlyValidatingElement = null ;
558
572
}
559
573
560
- this._mountClassComponent(element, this._context);
574
+ this._mountClassComponent(elementType, element, this._context);
561
575
} else {
562
- const prevDispatcher = ReactCurrentDispatcher . current ;
563
- ReactCurrentDispatcher . current = this . _dispatcher ;
564
- this . _prepareToUseHooks ( element . type ) ;
565
- try {
566
- if ( isForwardRef ( element ) ) {
567
- this . _rendered = element . type . render ( element . props , element . ref ) ;
568
- } else {
569
- this . _rendered = element . type . call (
570
- undefined ,
571
- element . props ,
572
- this . _context ,
573
- ) ;
576
+ let shouldRender = true ;
577
+ if (
578
+ isMemo ( element . type ) &&
579
+ elementType === this . _previousComponentIdentity &&
580
+ previousElement !== null
581
+ ) {
582
+ // This is a Memo component that is being re-rendered.
583
+ const compare = element . type . compare || shallowEqual ;
584
+ if ( compare ( previousElement . props , element . props ) ) {
585
+ shouldRender = false ;
586
+ }
587
+ }
588
+ if ( shouldRender ) {
589
+ const prevDispatcher = ReactCurrentDispatcher . current ;
590
+ ReactCurrentDispatcher . current = this . _dispatcher ;
591
+ this . _prepareToUseHooks ( elementType ) ;
592
+ try {
593
+ // elementType could still be a ForwardRef if it was
594
+ // nested inside Memo.
595
+ if ( elementType . $$typeof === ForwardRef ) {
596
+ invariant (
597
+ typeof elementType . render === 'function' ,
598
+ 'forwardRef requires a render function but was given %s.' ,
599
+ typeof elementType . render ,
600
+ ) ;
601
+ this . _rendered = elementType . render . call (
602
+ undefined ,
603
+ element . props ,
604
+ element . ref ,
605
+ ) ;
606
+ } else {
607
+ this . _rendered = elementType ( element . props , this. _context ) ;
608
+ }
609
+ } finally {
610
+ ReactCurrentDispatcher . current = prevDispatcher ;
574
611
}
575
- } finally {
576
- ReactCurrentDispatcher . current = prevDispatcher ;
612
+ this . _finishHooks ( element , context ) ;
577
613
}
578
- this._finishHooks(element, context);
579
614
}
580
615
}
581
616
@@ -601,7 +636,11 @@ class ReactShallowRenderer {
601
636
this . _instance = null ;
602
637
}
603
638
604
- _mountClassComponent ( element : ReactElement , context : null | Object ) {
639
+ _mountClassComponent (
640
+ elementType : Function ,
641
+ element : ReactElement ,
642
+ context : null | Object ,
643
+ ) {
605
644
this . _instance . context = context ;
606
645
this . _instance . props = element . props ;
607
646
this . _instance . state = this . _instance . state || null ;
@@ -616,7 +655,7 @@ class ReactShallowRenderer {
616
655
// In order to support react-lifecycles-compat polyfilled components,
617
656
// Unsafe lifecycles should not be invoked for components using the new APIs.
618
657
if (
619
- typeof element . type . getDerivedStateFromProps !== 'function' &&
658
+ typeof elementType . getDerivedStateFromProps !== 'function' &&
620
659
typeof this . _instance . getSnapshotBeforeUpdate !== 'function'
621
660
) {
622
661
if ( typeof this . _instance . componentWillMount === 'function' ) {
@@ -638,8 +677,12 @@ class ReactShallowRenderer {
638
677
// because DOM refs are not available.
639
678
}
640
679
641
- _updateClassComponent ( element : ReactElement , context : null | Object ) {
642
- const { props , type } = element;
680
+ _updateClassComponent (
681
+ elementType : Function ,
682
+ element : ReactElement ,
683
+ context : null | Object ,
684
+ ) {
685
+ const { props } = element;
643
686
644
687
const oldState = this._instance.state || emptyObject;
645
688
const oldProps = this._instance.props;
@@ -648,7 +691,7 @@ class ReactShallowRenderer {
648
691
// In order to support react-lifecycles-compat polyfilled components,
649
692
// Unsafe lifecycles should not be invoked for components using the new APIs.
650
693
if (
651
- typeof element . type . getDerivedStateFromProps !== 'function' &&
694
+ typeof elementType . getDerivedStateFromProps !== 'function' &&
652
695
typeof this . _instance . getSnapshotBeforeUpdate !== 'function'
653
696
) {
654
697
if ( typeof this . _instance . componentWillReceiveProps === 'function' ) {
@@ -664,8 +707,8 @@ class ReactShallowRenderer {
664
707
665
708
// Read state after cWRP in case it calls setState
666
709
let state = this . _newState || oldState ;
667
- if ( typeof type . getDerivedStateFromProps === 'function ') {
668
- const partialState = type . getDerivedStateFromProps . call (
710
+ if ( typeof elementType . getDerivedStateFromProps === 'function ') {
711
+ const partialState = elementType . getDerivedStateFromProps . call (
669
712
null ,
670
713
props ,
671
714
state ,
@@ -685,7 +728,10 @@ class ReactShallowRenderer {
685
728
state ,
686
729
context ,
687
730
) ;
688
- } else if (type.prototype && type . prototype . isPureReactComponent ) {
731
+ } else if (
732
+ elementType.prototype &&
733
+ elementType . prototype . isPureReactComponent
734
+ ) {
689
735
shouldUpdate =
690
736
! shallowEqual ( oldProps , props ) || ! shallowEqual ( oldState , state ) ;
691
737
}
@@ -694,7 +740,7 @@ class ReactShallowRenderer {
694
740
// In order to support react-lifecycles-compat polyfilled components,
695
741
// Unsafe lifecycles should not be invoked for components using the new APIs.
696
742
if (
697
- typeof element . type . getDerivedStateFromProps !== 'function' &&
743
+ typeof elementType . getDerivedStateFromProps !== 'function' &&
698
744
typeof this . _instance . getSnapshotBeforeUpdate !== 'function'
699
745
) {
700
746
if ( typeof this . _instance . componentWillUpdate === 'function' ) {
@@ -729,7 +775,8 @@ function getDisplayName(element) {
729
775
} else if (typeof element.type === 'string') {
730
776
return element . type ;
731
777
} else {
732
- return element . type . displayName || element . type . name || 'Unknown' ;
778
+ const elementType = isMemo ( element . type ) ? element . type . type : element . type ;
779
+ return elementType . displayName || elementType . name || 'Unknown' ;
733
780
}
734
781
}
735
782
0 commit comments