@@ -91,6 +91,17 @@ function areHookInputsEqual(
91
91
return true ;
92
92
}
93
93
94
+ function shouldRunEffectsBasedOnInputs (
95
+ before : Array < mixed > ,
96
+ after : Array < mixed > | void | null ,
97
+ ) {
98
+ if ( after == null || before . length !== after . length ) {
99
+ return true ;
100
+ }
101
+
102
+ return before . some ( ( value , i ) => ( after : any ) [ i ] !== value ) ;
103
+ }
104
+
94
105
class Updater {
95
106
constructor ( renderer ) {
96
107
this . _renderer = renderer ;
@@ -172,12 +183,21 @@ function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
172
183
return typeof action === 'function' ? action ( state ) : action ;
173
184
}
174
185
186
+ type ShallowRenderOptions = {
187
+ callEffects : boolean ,
188
+ } ;
189
+
190
+ const isEffectHook = Symbol();
191
+
175
192
class ReactShallowRenderer {
176
- static createRenderer = function ( ) {
177
- return new ReactShallowRenderer ( ) ;
193
+ static createRenderer = function (
194
+ options : ShallowRenderOptions = { callEffects : false } ,
195
+ ) {
196
+ return new ReactShallowRenderer ( options ) ;
178
197
} ;
179
198
180
- constructor ( ) {
199
+ constructor ( options : ShallowRenderOptions ) {
200
+ this . _options = options ;
181
201
this . _reset ( ) ;
182
202
}
183
203
@@ -199,6 +219,7 @@ class ReactShallowRenderer {
199
219
this . _numberOfReRenders = 0 ;
200
220
}
201
221
222
+ _options: ShallowRenderOptions;
202
223
_context: null | Object;
203
224
_newState: null | Object;
204
225
_instance: any;
@@ -308,6 +329,54 @@ class ReactShallowRenderer {
308
329
) ;
309
330
} ;
310
331
332
+ const useGenericEffect = (
333
+ create: () => ( ( ) => void ) | void ,
334
+ inputs : Array < mixed > | void | null,
335
+ isLayoutEffect: boolean,
336
+ ) => {
337
+ this . _validateCurrentlyRenderingComponent ( ) ;
338
+ this . _createWorkInProgressHook ( ) ;
339
+
340
+ if ( this . _workInProgressHook !== null ) {
341
+ if ( this . _workInProgressHook . memoizedState == null ) {
342
+ this . _workInProgressHook . memoizedState = {
343
+ isEffectHook,
344
+ isLayoutEffect,
345
+ create,
346
+ inputs,
347
+ cleanup : null ,
348
+ run : true ,
349
+ } ;
350
+ } else {
351
+ const { memoizedState } = this._workInProgressHook;
352
+ this._workInProgressHook.memoizedState = {
353
+ isEffectHook ,
354
+ isLayoutEffect ,
355
+ create ,
356
+ inputs ,
357
+ cleanup : memoizedState . cleanup ,
358
+ run :
359
+ inputs == null ||
360
+ shouldRunEffectsBasedOnInputs ( inputs , memoizedState . inputs ) ,
361
+ } ;
362
+ }
363
+ }
364
+ } ;
365
+
366
+ const useEffect = (
367
+ create : ( ) => ( ( ) => void ) | void ,
368
+ inputs : Array < mixed > | void | null,
369
+ ) => {
370
+ useGenericEffect ( create , inputs , false ) ;
371
+ } ;
372
+
373
+ const useLayoutEffect = (
374
+ create: () => ( ( ) => void ) | void ,
375
+ inputs : Array < mixed > | void | null,
376
+ ) => {
377
+ useGenericEffect ( create , inputs , true ) ;
378
+ } ;
379
+
311
380
const useMemo = < T > (
312
381
nextCreate: () => T ,
313
382
deps : Array < mixed > | void | null,
@@ -374,9 +443,9 @@ class ReactShallowRenderer {
374
443
return readContext ( context ) ;
375
444
} ,
376
445
useDebugValue: noOp,
377
- useEffect: noOp,
446
+ useEffect: this._options.callEffects ? useEffect : noOp,
378
447
useImperativeHandle: noOp,
379
- useLayoutEffect: noOp,
448
+ useLayoutEffect: this._options.callEffects ? useLayoutEffect : noOp,
380
449
useMemo,
381
450
useReducer,
382
451
useRef,
@@ -619,6 +688,7 @@ class ReactShallowRenderer {
619
688
ReactCurrentDispatcher . current = prevDispatcher ;
620
689
}
621
690
this . _finishHooks ( element , context ) ;
691
+ this . _callEffectsIfDesired ( ) ;
622
692
}
623
693
}
624
694
}
@@ -679,6 +749,36 @@ class ReactShallowRenderer {
679
749
// because DOM refs are not available.
680
750
}
681
751
752
+ _callEffectsIfDesired ( ) {
753
+ if ( ! this . _options . callEffects ) {
754
+ return ;
755
+ }
756
+
757
+ this._callEffects(true);
758
+ this._callEffects(false);
759
+ }
760
+
761
+ _callEffects ( callLayoutEffects : boolean ) {
762
+ for (
763
+ let hook = this . _firstWorkInProgressHook ;
764
+ hook !== null ;
765
+ hook = hook . next
766
+ ) {
767
+ const { memoizedState} = hook ;
768
+ if (
769
+ memoizedState != null &&
770
+ memoizedState . isEffectHook === isEffectHook &&
771
+ memoizedState . isLayoutEffect === callLayoutEffects &&
772
+ memoizedState . run
773
+ ) {
774
+ if ( memoizedState . cleanup ) {
775
+ memoizedState . cleanup ( ) ;
776
+ }
777
+ memoizedState . cleanup = memoizedState . create ( ) ;
778
+ }
779
+ }
780
+ }
781
+
682
782
_updateClassComponent(
683
783
elementType: Function,
684
784
element: ReactElement,
0 commit comments