88 */
99
1010import React from 'react' ;
11- import { isForwardRef } from 'react-is' ;
11+ import { isForwardRef , isMemo , ForwardRef } from 'react-is' ;
1212import describeComponentFrame from 'shared/describeComponentFrame' ;
1313import getComponentName from 'shared/getComponentName' ;
1414import shallowEqual from 'shared/shallowEqual' ;
@@ -500,7 +500,8 @@ class ReactShallowRenderer {
500500 element . type ,
501501 ) ;
502502 invariant (
503- isForwardRef ( element ) || typeof element . type === 'function' ,
503+ isForwardRef ( element ) ||
504+ ( typeof element . type === 'function' || isMemo ( element . type ) ) ,
504505 'ReactShallowRenderer render(): Shallow rendering works only with custom ' +
505506 'components, but the provided element type was `%s`.' ,
506507 Array . isArray ( element . type )
@@ -514,22 +515,36 @@ class ReactShallowRenderer {
514515 return ;
515516 }
516517
518+ const elementType = isMemo(element.type) ? element.type.type : element.type;
519+ const previousElement = this._element;
520+
517521 this._rendering = true;
518522 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+ }
520536
521537 if (this._instance) {
522- this . _updateClassComponent ( element , this . _context ) ;
538+ this . _updateClassComponent ( elementType , element , this . _context ) ;
523539 } else {
524- if ( shouldConstruct ( element . type ) ) {
525- this . _instance = new element . type (
540+ if ( shouldConstruct ( elementType ) ) {
541+ this . _instance = new elementType (
526542 element . props ,
527543 this . _context ,
528544 this . _updater ,
529545 ) ;
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 (
533548 null ,
534549 element . props ,
535550 this . _instance . state ,
@@ -543,39 +558,59 @@ class ReactShallowRenderer {
543558 }
544559 }
545560
546- if ( element . type . hasOwnProperty ( ' contextTypes ' ) ) {
561+ if ( elementType . contextTypes ) {
547562 currentlyValidatingElement = element ;
548-
549563 checkPropTypes (
550- element . type . contextTypes ,
564+ elementType . contextTypes ,
551565 this . _context ,
552566 'context' ,
553- getName ( element . type , this . _instance ) ,
567+ getName ( elementType , this . _instance ) ,
554568 getStackAddendum ,
555569 ) ;
556570
557571 currentlyValidatingElement = null ;
558572 }
559573
560- this._mountClassComponent(element, this._context);
574+ this._mountClassComponent(elementType, element, this._context);
561575 } 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 ;
574611 }
575- } finally {
576- ReactCurrentDispatcher . current = prevDispatcher ;
612+ this . _finishHooks ( element , context ) ;
577613 }
578- this._finishHooks(element, context);
579614 }
580615 }
581616
@@ -601,7 +636,11 @@ class ReactShallowRenderer {
601636 this . _instance = null ;
602637 }
603638
604- _mountClassComponent ( element : ReactElement , context : null | Object ) {
639+ _mountClassComponent (
640+ elementType : Function ,
641+ element : ReactElement ,
642+ context : null | Object ,
643+ ) {
605644 this . _instance . context = context ;
606645 this . _instance . props = element . props ;
607646 this . _instance . state = this . _instance . state || null ;
@@ -616,7 +655,7 @@ class ReactShallowRenderer {
616655 // In order to support react-lifecycles-compat polyfilled components,
617656 // Unsafe lifecycles should not be invoked for components using the new APIs.
618657 if (
619- typeof element . type . getDerivedStateFromProps !== 'function' &&
658+ typeof elementType . getDerivedStateFromProps !== 'function' &&
620659 typeof this . _instance . getSnapshotBeforeUpdate !== 'function'
621660 ) {
622661 if ( typeof this . _instance . componentWillMount === 'function' ) {
@@ -638,8 +677,12 @@ class ReactShallowRenderer {
638677 // because DOM refs are not available.
639678 }
640679
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;
643686
644687 const oldState = this._instance.state || emptyObject;
645688 const oldProps = this._instance.props;
@@ -648,7 +691,7 @@ class ReactShallowRenderer {
648691 // In order to support react-lifecycles-compat polyfilled components,
649692 // Unsafe lifecycles should not be invoked for components using the new APIs.
650693 if (
651- typeof element . type . getDerivedStateFromProps !== 'function' &&
694+ typeof elementType . getDerivedStateFromProps !== 'function' &&
652695 typeof this . _instance . getSnapshotBeforeUpdate !== 'function'
653696 ) {
654697 if ( typeof this . _instance . componentWillReceiveProps === 'function' ) {
@@ -664,8 +707,8 @@ class ReactShallowRenderer {
664707
665708 // Read state after cWRP in case it calls setState
666709 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 (
669712 null ,
670713 props ,
671714 state ,
@@ -685,7 +728,10 @@ class ReactShallowRenderer {
685728 state ,
686729 context ,
687730 ) ;
688- } else if (type.prototype && type . prototype . isPureReactComponent ) {
731+ } else if (
732+ elementType.prototype &&
733+ elementType . prototype . isPureReactComponent
734+ ) {
689735 shouldUpdate =
690736 ! shallowEqual ( oldProps , props ) || ! shallowEqual ( oldState , state ) ;
691737 }
@@ -694,7 +740,7 @@ class ReactShallowRenderer {
694740 // In order to support react-lifecycles-compat polyfilled components,
695741 // Unsafe lifecycles should not be invoked for components using the new APIs.
696742 if (
697- typeof element . type . getDerivedStateFromProps !== 'function' &&
743+ typeof elementType . getDerivedStateFromProps !== 'function' &&
698744 typeof this . _instance . getSnapshotBeforeUpdate !== 'function'
699745 ) {
700746 if ( typeof this . _instance . componentWillUpdate === 'function' ) {
@@ -729,7 +775,8 @@ function getDisplayName(element) {
729775 } else if (typeof element.type === 'string') {
730776 return element . type ;
731777 } 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' ;
733780 }
734781}
735782
0 commit comments