@@ -727,39 +727,111 @@ describe('ReactNewContext', () => {
727727 expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 'Child' ) ] ) ;
728728 } ) ;
729729
730- it ( 'consumer bails out if children and value are unchanged (like sCU) ' , ( ) => {
730+ it ( 'consumer bails out if value is unchanged and something above bailed out ' , ( ) => {
731731 const Context = React . createContext ( 0 ) ;
732732
733- function Child ( ) {
734- ReactNoop . yield ( 'Child' ) ;
735- return < span prop = "Child" /> ;
736- }
737-
738- function renderConsumer ( context ) {
739- return < Child context = { context } /> ;
733+ function renderChildValue ( value ) {
734+ ReactNoop . yield ( 'Consumer' ) ;
735+ return < span prop = { value } /> ;
740736 }
741737
742- function App ( props ) {
743- ReactNoop . yield ( 'App' ) ;
738+ function ChildWithInlineRenderCallback ( ) {
739+ ReactNoop . yield ( 'ChildWithInlineRenderCallback' ) ;
740+ // Note: we are intentionally passing an inline arrow. Don't refactor.
744741 return (
745- < Context . Provider value = { props . value } >
746- < Context . Consumer > { renderConsumer } </ Context . Consumer >
747- </ Context . Provider >
742+ < Context . Consumer > { value => renderChildValue ( value ) } </ Context . Consumer >
748743 ) ;
749744 }
750745
751- // Initial mount
752- ReactNoop . render ( < App value = { 1 } /> ) ;
753- expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'App' , 'Child' ] ) ;
754- expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 'Child' ) ] ) ;
746+ function ChildWithCachedRenderCallback ( ) {
747+ ReactNoop . yield ( 'ChildWithCachedRenderCallback' ) ;
748+ return < Context . Consumer > { renderChildValue } </ Context . Consumer > ;
749+ }
755750
756- // Update
751+ class PureIndirection extends React . PureComponent {
752+ render ( ) {
753+ ReactNoop . yield ( 'PureIndirection' ) ;
754+ return (
755+ < React . Fragment >
756+ < ChildWithInlineRenderCallback />
757+ < ChildWithCachedRenderCallback />
758+ </ React . Fragment >
759+ ) ;
760+ }
761+ }
762+
763+ class App extends React . Component {
764+ render ( ) {
765+ ReactNoop . yield ( 'App' ) ;
766+ return (
767+ < Context . Provider value = { this . props . value } >
768+ < PureIndirection />
769+ </ Context . Provider >
770+ ) ;
771+ }
772+ }
773+
774+ // Initial mount
757775 ReactNoop . render ( < App value = { 1 } /> ) ;
758776 expect ( ReactNoop . flush ( ) ) . toEqual ( [
759777 'App' ,
760- // Child does not re-render
778+ 'PureIndirection' ,
779+ 'ChildWithInlineRenderCallback' ,
780+ 'Consumer' ,
781+ 'ChildWithCachedRenderCallback' ,
782+ 'Consumer' ,
761783 ] ) ;
762- expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 'Child' ) ] ) ;
784+ expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 1 ) , span ( 1 ) ] ) ;
785+
786+ // Update (bailout)
787+ ReactNoop . render ( < App value = { 1 } /> ) ;
788+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'App' ] ) ;
789+ expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 1 ) , span ( 1 ) ] ) ;
790+
791+ // Update (no bailout)
792+ ReactNoop . render ( < App value = { 2 } /> ) ;
793+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'App' , 'Consumer' , 'Consumer' ] ) ;
794+ expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 2 ) , span ( 2 ) ] ) ;
795+ } ) ;
796+
797+ // Context consumer bails out on propagating "deep" updates when `value` hasn't changed.
798+ // However, it doesn't bail out from rendering if the component above it re-rendered anyway.
799+ // If we bailed out on referential equality, it would be confusing that you
800+ // can call this.setState(), but an autobound render callback "blocked" the update.
801+ // https://github.com/facebook/react/pull/12470#issuecomment-376917711
802+ it ( 'consumer does not bail out if there were no bailouts above it' , ( ) => {
803+ const Context = React . createContext ( 0 ) ;
804+
805+ class App extends React . Component {
806+ state = {
807+ text : 'hello' ,
808+ } ;
809+
810+ renderConsumer = context => {
811+ ReactNoop . yield ( 'App#renderConsumer' ) ;
812+ return < span prop = { this . state . text } /> ;
813+ } ;
814+
815+ render ( ) {
816+ ReactNoop . yield ( 'App' ) ;
817+ return (
818+ < Context . Provider value = { this . props . value } >
819+ < Context . Consumer > { this . renderConsumer } </ Context . Consumer >
820+ </ Context . Provider >
821+ ) ;
822+ }
823+ }
824+
825+ // Initial mount
826+ let inst ;
827+ ReactNoop . render ( < App value = { 1 } ref = { ref => ( inst = ref ) } /> ) ;
828+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'App' , 'App#renderConsumer' ] ) ;
829+ expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 'hello' ) ] ) ;
830+
831+ // Update
832+ inst . setState ( { text : 'goodbye' } ) ;
833+ expect ( ReactNoop . flush ( ) ) . toEqual ( [ 'App' , 'App#renderConsumer' ] ) ;
834+ expect ( ReactNoop . getChildren ( ) ) . toEqual ( [ span ( 'goodbye' ) ] ) ;
763835 } ) ;
764836
765837 // This is a regression case for https://github.com/facebook/react/issues/12389.
0 commit comments