@@ -31,7 +31,7 @@ type Update<A> = {
3131} ;
3232
3333type UpdateQueue < A > = {
34- last : Update < A > | null ,
34+ first : Update < A > | null ,
3535 dispatch : any ,
3636} ;
3737
@@ -178,6 +178,10 @@ class ReactShallowRenderer {
178178 } ;
179179
180180 constructor ( ) {
181+ this . _reset ( ) ;
182+ }
183+
184+ _reset() {
181185 this . _context = null ;
182186 this . _element = null ;
183187 this . _instance = null ;
@@ -192,9 +196,7 @@ class ReactShallowRenderer {
192196 this . _isReRender = false ;
193197 this . _didScheduleRenderPhaseUpdate = false ;
194198 this . _renderPhaseUpdates = null ;
195- this . _currentlyRenderingComponent = null ;
196199 this . _numberOfReRenders = 0 ;
197- this . _previousComponentIdentity = null ;
198200 }
199201
200202 _context: null | Object;
@@ -208,16 +210,14 @@ class ReactShallowRenderer {
208210 _dispatcher: DispatcherType;
209211 _workInProgressHook: null | Hook;
210212 _firstWorkInProgressHook: null | Hook;
211- _currentlyRenderingComponent: null | Object;
212- _previousComponentIdentity: null | Object;
213213 _renderPhaseUpdates: Map< UpdateQueue < any > , Update< any > > | null ;
214214 _isReRender : boolean ;
215215 _didScheduleRenderPhaseUpdate : boolean ;
216216 _numberOfReRenders : number ;
217217
218218 _validateCurrentlyRenderingComponent ( ) {
219219 invariant (
220- this . _currentlyRenderingComponent !== null ,
220+ this . _rendering && ! this . _instance ,
221221 'Hooks can only be called inside the body of a function component. ' +
222222 '(https://fb.me/react-invalid-hook-call)' ,
223223 ) ;
@@ -232,33 +232,44 @@ class ReactShallowRenderer {
232232 this. _validateCurrentlyRenderingComponent ( ) ;
233233 this . _createWorkInProgressHook ( ) ;
234234 const workInProgressHook : Hook = ( this . _workInProgressHook : any ) ;
235+
235236 if ( this . _isReRender ) {
236- // This is a re-render. Apply the new render phase updates to the previous
237- // current hook.
237+ // This is a re-render.
238238 const queue : UpdateQueue < A > = ( workInProgressHook . queue : any ) ;
239239 const dispatch : Dispatch < A > = ( queue . dispatch : any ) ;
240- if ( this . _renderPhaseUpdates !== null ) {
241- // Render phase updates are stored in a map of queue -> linked list
242- const firstRenderPhaseUpdate = this . _renderPhaseUpdates . get ( queue ) ;
243- if ( firstRenderPhaseUpdate !== undefined ) {
244- ( this . _renderPhaseUpdates : any ) . delete ( queue ) ;
245- let newState = workInProgressHook . memoizedState ;
246- let update = firstRenderPhaseUpdate ;
247- do {
248- // Process this render phase update. We don't have to check the
249- // priority because it will always be the same as the current
250- // render's.
251- const action = update . action ;
252- newState = reducer ( newState , action ) ;
253- update = update . next ;
254- } while ( update !== null ) ;
255-
256- workInProgressHook . memoizedState = newState ;
257-
258- return [ newState , dispatch ] ;
240+ if ( this . _numberOfReRenders > 0 ) {
241+ // Apply the new render phase updates to the previous current hook.
242+ if ( this . _renderPhaseUpdates !== null ) {
243+ // Render phase updates are stored in a map of queue -> linked list
244+ const firstRenderPhaseUpdate = this . _renderPhaseUpdates . get ( queue ) ;
245+ if ( firstRenderPhaseUpdate !== undefined ) {
246+ ( this . _renderPhaseUpdates : any ) . delete ( queue ) ;
247+ let newState = workInProgressHook . memoizedState ;
248+ let update = firstRenderPhaseUpdate ;
249+ do {
250+ const action = update . action ;
251+ newState = reducer ( newState , action ) ;
252+ update = update . next ;
253+ } while ( update !== null ) ;
254+ workInProgressHook . memoizedState = newState ;
255+ return [ newState , dispatch ] ;
256+ }
259257 }
258+ return [ workInProgressHook . memoizedState , dispatch ] ;
260259 }
261- return [ workInProgressHook . memoizedState , dispatch ] ;
260+ // Process updates outside of render
261+ let newState = workInProgressHook . memoizedState ;
262+ let update = queue . first ;
263+ if ( update !== null ) {
264+ do {
265+ const action = update . action ;
266+ newState = reducer ( newState , action ) ;
267+ update = update . next ;
268+ } while ( update !== null ) ;
269+ queue . first = null ;
270+ workInProgressHook . memoizedState = newState ;
271+ }
272+ return [ newState , dispatch ] ;
262273 } else {
263274 let initialState ;
264275 if ( reducer === basicStateReducer ) {
@@ -273,16 +284,12 @@ class ReactShallowRenderer {
273284 }
274285 workInProgressHook.memoizedState = initialState;
275286 const queue: UpdateQueue< A > = (workInProgressHook.queue = {
276- last : null ,
287+ first : null ,
277288 dispatch : null ,
278289 } );
279290 const dispatch: Dispatch<
280291 A ,
281- > = (queue.dispatch = (this._dispatchAction.bind(
282- this,
283- (this._currentlyRenderingComponent: any),
284- queue,
285- ): any));
292+ > = (queue.dispatch = (this._dispatchAction.bind(this, queue): any));
286293 return [workInProgressHook.memoizedState, dispatch];
287294 }
288295 } ;
@@ -373,18 +380,14 @@ class ReactShallowRenderer {
373380 } ;
374381 }
375382
376- _dispatchAction < A > (
377- componentIdentity: Object,
378- queue: UpdateQueue< A > ,
379- action: A,
380- ) {
383+ _dispatchAction < A > (queue: UpdateQueue< A > , action: A) {
381384 invariant (
382385 this . _numberOfReRenders < RE_RENDER_LIMIT ,
383386 'Too many re-renders. React limits the number of renders to prevent ' +
384387 'an infinite loop.' ,
385388 ) ;
386389
387- if ( componentIdentity === this . _currentlyRenderingComponent ) {
390+ if ( this . _rendering ) {
388391 // This is a render phase update. Stash it in a lazily-created map of
389392 // queue -> linked list of updates. After this render pass, we'll restart
390393 // and apply the stashed updates on top of the work-in-progress hook.
@@ -409,9 +412,24 @@ class ReactShallowRenderer {
409412 lastRenderPhaseUpdate . next = update ;
410413 }
411414 } else {
412- // This means an update has happened after the function component has
413- // returned. On the server this is a no-op. In React Fiber, the update
414- // would be scheduled for a future render.
415+ const update : Update < A > = {
416+ action,
417+ next : null ,
418+ } ;
419+
420+ // Append the update to the end of the list.
421+ let last = queue . first ;
422+ if ( last === null ) {
423+ queue . first = update ;
424+ } else {
425+ while ( last . next !== null ) {
426+ last = last . next ;
427+ }
428+ last.next = update;
429+ }
430+
431+ // Re-render now.
432+ this.render(this._element, this._context);
415433 }
416434 }
417435
@@ -441,17 +459,6 @@ class ReactShallowRenderer {
441459 return this . _workInProgressHook ;
442460 }
443461
444- _prepareToUseHooks(componentIdentity: Object): void {
445- if (
446- this . _previousComponentIdentity !== null &&
447- this . _previousComponentIdentity !== componentIdentity
448- ) {
449- this . _firstWorkInProgressHook = null ;
450- }
451- this._currentlyRenderingComponent = componentIdentity;
452- this._previousComponentIdentity = componentIdentity;
453- }
454-
455462 _finishHooks ( element : ReactElement , context : null | Object ) {
456463 if ( this . _didScheduleRenderPhaseUpdate ) {
457464 // Updates were scheduled during the render phase. They are stored in
@@ -466,7 +473,6 @@ class ReactShallowRenderer {
466473 this . _rendering = false ;
467474 this . render ( element , context ) ;
468475 } else {
469- this . _currentlyRenderingComponent = null ;
470476 this . _workInProgressHook = null ;
471477 this . _renderPhaseUpdates = null ;
472478 this . _numberOfReRenders = 0 ;
@@ -514,6 +520,9 @@ class ReactShallowRenderer {
514520 if ( this . _rendering ) {
515521 return ;
516522 }
523+ if (this._element != null && this . _element . type !== element . type ) {
524+ this . _reset ( ) ;
525+ }
517526
518527 const elementType = isMemo(element.type) ? element.type.type : element.type;
519528 const previousElement = this._element;
@@ -574,11 +583,7 @@ class ReactShallowRenderer {
574583 this._mountClassComponent(elementType, element, this._context);
575584 } else {
576585 let shouldRender = true ;
577- if (
578- isMemo ( element . type ) &&
579- elementType === this . _previousComponentIdentity &&
580- previousElement !== null
581- ) {
586+ if ( isMemo ( element . type ) && previousElement !== null ) {
582587 // This is a Memo component that is being re-rendered.
583588 const compare = element . type . compare || shallowEqual ;
584589 if ( compare ( previousElement . props , element . props ) ) {
@@ -588,7 +593,6 @@ class ReactShallowRenderer {
588593 if ( shouldRender ) {
589594 const prevDispatcher = ReactCurrentDispatcher . current ;
590595 ReactCurrentDispatcher . current = this . _dispatcher ;
591- this . _prepareToUseHooks ( elementType ) ;
592596 try {
593597 // elementType could still be a ForwardRef if it was
594598 // nested inside Memo.
@@ -626,14 +630,7 @@ class ReactShallowRenderer {
626630 this . _instance . componentWillUnmount ( ) ;
627631 }
628632 }
629-
630- this . _firstWorkInProgressHook = null ;
631- this . _previousComponentIdentity = null ;
632- this . _context = null ;
633- this . _element = null ;
634- this . _newState = null ;
635- this . _rendered = null ;
636- this . _instance = null ;
633+ this . _reset ( ) ;
637634 }
638635
639636 _mountClassComponent (
0 commit comments