@@ -31,7 +31,7 @@ type Update<A> = {
31
31
} ;
32
32
33
33
type UpdateQueue < A > = {
34
- last : Update < A > | null ,
34
+ first : Update < A > | null ,
35
35
dispatch : any ,
36
36
} ;
37
37
@@ -178,6 +178,10 @@ class ReactShallowRenderer {
178
178
} ;
179
179
180
180
constructor ( ) {
181
+ this . _reset ( ) ;
182
+ }
183
+
184
+ _reset() {
181
185
this . _context = null ;
182
186
this . _element = null ;
183
187
this . _instance = null ;
@@ -192,9 +196,7 @@ class ReactShallowRenderer {
192
196
this . _isReRender = false ;
193
197
this . _didScheduleRenderPhaseUpdate = false ;
194
198
this . _renderPhaseUpdates = null ;
195
- this . _currentlyRenderingComponent = null ;
196
199
this . _numberOfReRenders = 0 ;
197
- this . _previousComponentIdentity = null ;
198
200
}
199
201
200
202
_context: null | Object;
@@ -208,16 +210,14 @@ class ReactShallowRenderer {
208
210
_dispatcher: DispatcherType;
209
211
_workInProgressHook: null | Hook;
210
212
_firstWorkInProgressHook: null | Hook;
211
- _currentlyRenderingComponent: null | Object;
212
- _previousComponentIdentity: null | Object;
213
213
_renderPhaseUpdates: Map< UpdateQueue < any > , Update< any > > | null ;
214
214
_isReRender : boolean ;
215
215
_didScheduleRenderPhaseUpdate : boolean ;
216
216
_numberOfReRenders : number ;
217
217
218
218
_validateCurrentlyRenderingComponent ( ) {
219
219
invariant (
220
- this . _currentlyRenderingComponent !== null ,
220
+ this . _rendering && ! this . _instance ,
221
221
'Hooks can only be called inside the body of a function component. ' +
222
222
'(https://fb.me/react-invalid-hook-call)' ,
223
223
) ;
@@ -232,33 +232,44 @@ class ReactShallowRenderer {
232
232
this. _validateCurrentlyRenderingComponent ( ) ;
233
233
this . _createWorkInProgressHook ( ) ;
234
234
const workInProgressHook : Hook = ( this . _workInProgressHook : any ) ;
235
+
235
236
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.
238
238
const queue : UpdateQueue < A > = ( workInProgressHook . queue : any ) ;
239
239
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
+ }
259
257
}
258
+ return [ workInProgressHook . memoizedState , dispatch ] ;
260
259
}
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 ] ;
262
273
} else {
263
274
let initialState ;
264
275
if ( reducer === basicStateReducer ) {
@@ -273,16 +284,12 @@ class ReactShallowRenderer {
273
284
}
274
285
workInProgressHook.memoizedState = initialState;
275
286
const queue: UpdateQueue< A > = (workInProgressHook.queue = {
276
- last : null ,
287
+ first : null ,
277
288
dispatch : null ,
278
289
} );
279
290
const dispatch: Dispatch<
280
291
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));
286
293
return [workInProgressHook.memoizedState, dispatch];
287
294
}
288
295
} ;
@@ -373,18 +380,14 @@ class ReactShallowRenderer {
373
380
} ;
374
381
}
375
382
376
- _dispatchAction < A > (
377
- componentIdentity: Object,
378
- queue: UpdateQueue< A > ,
379
- action: A,
380
- ) {
383
+ _dispatchAction < A > (queue: UpdateQueue< A > , action: A) {
381
384
invariant (
382
385
this . _numberOfReRenders < RE_RENDER_LIMIT ,
383
386
'Too many re-renders. React limits the number of renders to prevent ' +
384
387
'an infinite loop.' ,
385
388
) ;
386
389
387
- if ( componentIdentity === this . _currentlyRenderingComponent ) {
390
+ if ( this . _rendering ) {
388
391
// This is a render phase update. Stash it in a lazily-created map of
389
392
// queue -> linked list of updates. After this render pass, we'll restart
390
393
// and apply the stashed updates on top of the work-in-progress hook.
@@ -409,9 +412,24 @@ class ReactShallowRenderer {
409
412
lastRenderPhaseUpdate . next = update ;
410
413
}
411
414
} 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);
415
433
}
416
434
}
417
435
@@ -441,17 +459,6 @@ class ReactShallowRenderer {
441
459
return this . _workInProgressHook ;
442
460
}
443
461
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
-
455
462
_finishHooks ( element : ReactElement , context : null | Object ) {
456
463
if ( this . _didScheduleRenderPhaseUpdate ) {
457
464
// Updates were scheduled during the render phase. They are stored in
@@ -466,7 +473,6 @@ class ReactShallowRenderer {
466
473
this . _rendering = false ;
467
474
this . render ( element , context ) ;
468
475
} else {
469
- this . _currentlyRenderingComponent = null ;
470
476
this . _workInProgressHook = null ;
471
477
this . _renderPhaseUpdates = null ;
472
478
this . _numberOfReRenders = 0 ;
@@ -514,6 +520,9 @@ class ReactShallowRenderer {
514
520
if ( this . _rendering ) {
515
521
return ;
516
522
}
523
+ if (this._element != null && this . _element . type !== element . type ) {
524
+ this . _reset ( ) ;
525
+ }
517
526
518
527
const elementType = isMemo(element.type) ? element.type.type : element.type;
519
528
const previousElement = this._element;
@@ -574,11 +583,7 @@ class ReactShallowRenderer {
574
583
this._mountClassComponent(elementType, element, this._context);
575
584
} else {
576
585
let shouldRender = true ;
577
- if (
578
- isMemo ( element . type ) &&
579
- elementType === this . _previousComponentIdentity &&
580
- previousElement !== null
581
- ) {
586
+ if ( isMemo ( element . type ) && previousElement !== null ) {
582
587
// This is a Memo component that is being re-rendered.
583
588
const compare = element . type . compare || shallowEqual ;
584
589
if ( compare ( previousElement . props , element . props ) ) {
@@ -588,7 +593,6 @@ class ReactShallowRenderer {
588
593
if ( shouldRender ) {
589
594
const prevDispatcher = ReactCurrentDispatcher . current ;
590
595
ReactCurrentDispatcher . current = this . _dispatcher ;
591
- this . _prepareToUseHooks ( elementType ) ;
592
596
try {
593
597
// elementType could still be a ForwardRef if it was
594
598
// nested inside Memo.
@@ -626,14 +630,7 @@ class ReactShallowRenderer {
626
630
this . _instance . componentWillUnmount ( ) ;
627
631
}
628
632
}
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 ( ) ;
637
634
}
638
635
639
636
_mountClassComponent (
0 commit comments