[crud] Revert CRUD overload (#32741)
Cleans up this experiment. After some internal experimentation we are deprioritizing this project for now and may revisit it at a later point.
lauren committed
Mar 26, 2025 at 12:04 UTC
313332d111a2fba2db94c584334d8895e8d73c61
17 files changed
+61
-1453
packages/react-debug-tools/src/ReactDebugHooks.js
+2
-5
@@ -374,11 +374,8 @@ function useInsertionEffect(
374
}
375
376
function useEffect(
377
- create: (() => (() => void) | void) | (() => {...} | void | null),
378
- createDeps: Array<mixed> | void | null,
379
- update?: ((resource: {...} | void | null) => void) | void,
380
- updateDeps?: Array<mixed> | void | null,
381
- destroy?: ((resource: {...} | void | null) => void) | void,
377
+ create: () => (() => void) | void,
378
+ deps: Array<mixed> | void | null,
379
): void {
380
nextHook();
381
hookLog.push({
packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js
-46
@@ -653,52 +653,6 @@ describe('ReactDOMServerHooks', () => {
653
});
654
});
655
656
- describe('useEffect with CRUD overload', () => {
657
- gate(flags => {
658
- if (flags.enableUseEffectCRUDOverload) {
659
- const yields = [];
660
- itRenders(
661
- 'should ignore resource effects on the server',
662
- async render => {
663
- function Counter(props) {
664
- useEffect(
665
- () => {
666
- yieldValue('created on client');
667
- return {resource_counter: props.count};
668
- },
669
- [props.count],
670
- resource => {
671
- resource.resource_counter = props.count;
672
- yieldValue('updated on client');
673
- },
674
- [props.count],
675
- () => {
676
- yieldValue('cleanup on client');
677
- },
678
- );
679
- return <Text text={'Count: ' + props.count} />;
680
- }
681
-
682
- const domNode = await render(<Counter count={0} />);
683
- yields.push(clearLog());
684
- expect(domNode.tagName).toEqual('SPAN');
685
- expect(domNode.textContent).toEqual('Count: 0');
686
- },
687
- );
688
-
689
- it('verifies yields in order', () => {
690
- expect(yields).toEqual([
691
- ['Count: 0'], // server render
692
- ['Count: 0'], // server stream
693
- ['Count: 0', 'created on client'], // clean render
694
- ['Count: 0', 'created on client'], // hydrated render
695
- // nothing yielded for bad markup
696
- ]);
697
- });
698
- }
699
- });
700
- });
701
-
656
describe('useContext', () => {
657
itThrowsWhenRendering(
658
'if used inside a class component',
packages/react-reconciler/src/ReactFiberCallUserSpace.js
+5
-50
@@ -14,11 +14,6 @@ import type {CapturedValue} from './ReactCapturedValue';
14
15
import {isRendering, setIsRendering} from './ReactCurrentFiber';
16
import {captureCommitPhaseError} from './ReactFiberWorkLoop';
17
-import {
18
- ResourceEffectIdentityKind,
19
- ResourceEffectUpdateKind,
20
-} from './ReactFiberHooks';
21
-import {enableUseEffectCRUDOverload} from 'shared/ReactFeatureFlags';
17
18
// These indirections exists so we can exclude its stack frame in DEV (and anything below it).
19
// TODO: Consider marking the whole bundle instead of these boundaries.
@@ -184,51 +179,11 @@ const callCreate = {
179
'react-stack-bottom-frame': function (
180
effect: Effect,
181
): (() => void) | {...} | void | null {
187
- if (!enableUseEffectCRUDOverload) {
188
- if (effect.resourceKind != null) {
189
- if (__DEV__) {
190
- console.error(
191
- 'Expected only SimpleEffects when enableUseEffectCRUDOverload is disabled, ' +
192
- 'got %s',
193
- effect.resourceKind,
194
- );
195
- }
196
- }
197
- const create = effect.create;
198
- const inst = effect.inst;
199
- // $FlowFixMe[not-a-function] (@poteto)
200
- const destroy = create();
201
- // $FlowFixMe[incompatible-type] (@poteto)
202
- inst.destroy = destroy;
203
- return destroy;
204
- } else {
205
- if (effect.resourceKind == null) {
206
- const create = effect.create;
207
- const inst = effect.inst;
208
- const destroy = create();
209
- inst.destroy = destroy;
210
- return destroy;
211
- }
212
- switch (effect.resourceKind) {
213
- case ResourceEffectIdentityKind: {
214
- return effect.create();
215
- }
216
- case ResourceEffectUpdateKind: {
217
- if (typeof effect.update === 'function') {
218
- effect.update(effect.inst.resource);
219
- }
220
- break;
221
- }
222
- default: {
223
- if (__DEV__) {
224
- console.error(
225
- 'Unhandled Effect kind %s. This is a bug in React.',
226
- effect.kind,
227
- );
228
- }
229
- }
230
- }
231
- }
182
+ const create = effect.create;
183
+ const inst = effect.inst;
184
+ const destroy = create();
185
+ inst.destroy = destroy;
186
+ return destroy;
187
},
188
};
189
packages/react-reconciler/src/ReactFiberCommitEffects.js
+7
-125
@@ -23,7 +23,6 @@ import {
23
enableProfilerCommitHooks,
24
enableProfilerNestedUpdatePhase,
25
enableSchedulingProfiler,
26
- enableUseEffectCRUDOverload,
26
enableViewTransition,
27
enableFragmentRefs,
28
} from 'shared/ReactFeatureFlags';
@@ -62,7 +61,6 @@ import {
61
Layout as HookLayout,
62
Insertion as HookInsertion,
63
Passive as HookPassive,
65
- HasEffect as HookHasEffect,
64
} from './ReactHookEffectTags';
65
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork';
66
import {
@@ -84,10 +82,6 @@ import {
82
} from './ReactFiberCallUserSpace';
83
84
import {runWithFiberInDEV} from './ReactCurrentFiber';
87
-import {
88
- ResourceEffectIdentityKind,
89
- ResourceEffectUpdateKind,
90
-} from './ReactFiberHooks';
85
86
function shouldProfile(current: Fiber): boolean {
87
return (
@@ -164,91 +158,19 @@ export function commitHookEffectListMount(
158
159
// Mount
160
let destroy;
167
- if (enableUseEffectCRUDOverload) {
168
- if (effect.resourceKind === ResourceEffectIdentityKind) {
169
- if (__DEV__) {
170
- effect.inst.resource = runWithFiberInDEV(
171
- finishedWork,
172
- callCreateInDEV,
173
- effect,
174
- );
175
- if (effect.inst.resource == null) {
176
- console.error(
177
- 'useEffect must provide a callback which returns a resource. ' +
178
- 'If a managed resource is not needed here, do not provide an updater or ' +
179
- 'destroy callback. Received %s',
180
- effect.inst.resource,
181
- );
182
- }
183
- } else {
184
- effect.inst.resource = effect.create();
185
- }
186
- destroy = effect.inst.destroy;
187
- }
188
- if (effect.resourceKind === ResourceEffectUpdateKind) {
189
- if (
190
- // We don't want to fire updates on remount during Activity
191
- (flags & HookHasEffect) > 0 &&
192
- typeof effect.update === 'function' &&
193
- effect.inst.resource != null
194
- ) {
195
- // TODO(@poteto) what about multiple updates?
196
- if (__DEV__) {
197
- runWithFiberInDEV(finishedWork, callCreateInDEV, effect);
198
- } else {
199
- effect.update(effect.inst.resource);
200
- }
201
- }
202
- }
203
- }
161
if (__DEV__) {
162
if ((flags & HookInsertion) !== NoHookEffect) {
163
setIsRunningInsertionEffect(true);
164
}
208
- if (enableUseEffectCRUDOverload) {
209
- if (effect.resourceKind == null) {
210
- destroy = runWithFiberInDEV(
211
- finishedWork,
212
- callCreateInDEV,
213
- effect,
214
- );
215
- }
216
- } else {
217
- destroy = runWithFiberInDEV(
218
- finishedWork,
219
- callCreateInDEV,
220
- effect,
221
- );
222
- }
165
+ destroy = runWithFiberInDEV(finishedWork, callCreateInDEV, effect);
166
if ((flags & HookInsertion) !== NoHookEffect) {
167
setIsRunningInsertionEffect(false);
168
}
169
} else {
227
- if (enableUseEffectCRUDOverload) {
228
- if (effect.resourceKind == null) {
229
- const create = effect.create;
230
- const inst = effect.inst;
231
- destroy = create();
232
- inst.destroy = destroy;
233
- }
234
- } else {
235
- if (effect.resourceKind != null) {
236
- if (__DEV__) {
237
- console.error(
238
- 'Expected only SimpleEffects when enableUseEffectCRUDOverload is disabled, ' +
239
- 'got %s',
240
- effect.resourceKind,
241
- );
242
- }
243
- }
244
- const create = effect.create;
245
- const inst = effect.inst;
246
- // $FlowFixMe[incompatible-type] (@poteto)
247
- // $FlowFixMe[not-a-function] (@poteto)
248
- destroy = create();
249
- // $FlowFixMe[incompatible-type] (@poteto)
250
- inst.destroy = destroy;
251
- }
170
+ const create = effect.create;
171
+ const inst = effect.inst;
172
+ destroy = create();
173
+ inst.destroy = destroy;
174
}
175
176
if (enableSchedulingProfiler) {
@@ -338,13 +260,7 @@ export function commitHookEffectListUnmount(
260
const inst = effect.inst;
261
const destroy = inst.destroy;
262
if (destroy !== undefined) {
341
- if (enableUseEffectCRUDOverload) {
342
- if (effect.resourceKind == null) {
343
- inst.destroy = undefined;
344
- }
345
- } else {
346
- inst.destroy = undefined;
347
- }
263
+ inst.destroy = undefined;
264
if (enableSchedulingProfiler) {
265
if ((flags & HookPassive) !== NoHookEffect) {
266
markComponentPassiveEffectUnmountStarted(finishedWork);
@@ -358,41 +274,7 @@ export function commitHookEffectListUnmount(
274
setIsRunningInsertionEffect(true);
275
}
276
}
361
- if (enableUseEffectCRUDOverload) {
362
- if (
363
- effect.resourceKind === ResourceEffectIdentityKind &&
364
- effect.inst.resource != null
365
- ) {
366
- safelyCallDestroy(
367
- finishedWork,
368
- nearestMountedAncestor,
369
- destroy,
370
- effect.inst.resource,
371
- );
372
- if (effect.next.resourceKind === ResourceEffectUpdateKind) {
373
- // $FlowFixMe[prop-missing] (@poteto)
374
- effect.next.update = undefined;
375
- } else {
376
- if (__DEV__) {
377
- console.error(
378
- 'Expected a ResourceEffectUpdateKind to follow ResourceEffectIdentityKind, ' +
379
- 'got %s. This is a bug in React.',
380
- effect.next.resourceKind,
381
- );
382
- }
383
- }
384
- effect.inst.resource = null;
385
- }
386
- if (effect.resourceKind == null) {
387
- safelyCallDestroy(
388
- finishedWork,
389
- nearestMountedAncestor,
390
- destroy,
391
- );
392
- }
393
- } else {
394
- safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
395
- }
277
+ safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
278
if (__DEV__) {
279
if ((flags & HookInsertion) !== NoHookEffect) {
280
setIsRunningInsertionEffect(false);
packages/react-reconciler/src/ReactFiberHooks.js
+41
-426
@@ -43,7 +43,6 @@ import {
43
enableSchedulingProfiler,
44
enableTransitionTracing,
45
enableUseEffectEventHook,
46
- enableUseEffectCRUDOverload,
46
enableLegacyCache,
47
disableLegacyMode,
48
enableNoCloningMemoCache,
@@ -219,43 +218,16 @@ export type Hook = {
218
// the additional memory and we can follow up with performance
219
// optimizations later.
220
type EffectInstance = {
222
- resource: {...} | void | null,
223
- destroy: void | (() => void) | ((resource: {...} | void | null) => void),
221
+ destroy: void | (() => void),
222
};
223
226
-export const ResourceEffectIdentityKind: 0 = 0;
227
-export const ResourceEffectUpdateKind: 1 = 1;
228
-export type EffectKind =
229
- | typeof ResourceEffectIdentityKind
230
- | typeof ResourceEffectUpdateKind;
231
-export type Effect =
232
- | SimpleEffect
233
- | ResourceEffectIdentity
234
- | ResourceEffectUpdate;
235
-export type SimpleEffect = {
224
+export type Effect = {
225
tag: HookFlags,
226
inst: EffectInstance,
227
create: () => (() => void) | void,
228
deps: Array<mixed> | void | null,
229
next: Effect,
230
};
242
-export type ResourceEffectIdentity = {
243
- resourceKind: typeof ResourceEffectIdentityKind,
244
- tag: HookFlags,
245
- inst: EffectInstance,
246
- create: () => {...} | void | null,
247
- deps: Array<mixed> | void | null,
248
- next: Effect,
249
-};
250
-export type ResourceEffectUpdate = {
251
- resourceKind: typeof ResourceEffectUpdateKind,
252
- tag: HookFlags,
253
- inst: EffectInstance,
254
- update: ((resource: {...} | void | null) => void) | void,
255
- deps: Array<mixed> | void | null,
256
- next: Effect,
257
- identity: ResourceEffectIdentity,
258
-};
231
232
type StoreInstance<T> = {
233
value: T,
@@ -377,23 +349,6 @@ function checkDepsAreArrayDev(deps: mixed): void {
349
}
350
}
351
380
-function checkDepsAreNonEmptyArrayDev(deps: mixed): void {
381
- if (__DEV__) {
382
- if (
383
- deps !== undefined &&
384
- deps !== null &&
385
- isArray(deps) &&
386
- deps.length === 0
387
- ) {
388
- console.error(
389
- '%s received a dependency array with no dependencies. When ' +
390
- 'specified, the dependency array must have at least one dependency.',
391
- currentHookNameInDev,
392
- );
393
- }
394
- }
395
-}
396
-
352
function warnOnHookMismatchInDev(currentHookName: HookType): void {
353
if (__DEV__) {
354
const componentName = getComponentNameFromFiber(currentlyRenderingFiber);
@@ -2540,15 +2495,12 @@ function pushSimpleEffect(
2495
tag: HookFlags,
2496
inst: EffectInstance,
2497
create: () => (() => void) | void,
2543
- createDeps: Array<mixed> | void | null,
2544
- update?: ((resource: {...} | void | null) => void) | void,
2545
- updateDeps?: Array<mixed> | void | null,
2546
- destroy?: ((resource: {...} | void | null) => void) | void,
2498
+ deps: Array<mixed> | void | null,
2499
): Effect {
2500
const effect: Effect = {
2501
tag,
2502
create,
2551
- deps: createDeps,
2503
+ deps,
2504
inst,
2505
// Circular
2506
next: (null: any),
@@ -2556,39 +2508,6 @@ function pushSimpleEffect(
2508
return pushEffectImpl(effect);
2509
}
2510
2559
-function pushResourceEffect(
2560
- identityTag: HookFlags,
2561
- updateTag: HookFlags,
2562
- inst: EffectInstance,
2563
- create: () => {...} | void | null,
2564
- createDeps: Array<mixed> | void | null,
2565
- update: ((resource: {...} | void | null) => void) | void,
2566
- updateDeps: Array<mixed> | void | null,
2567
-): Effect {
2568
- const effectIdentity: ResourceEffectIdentity = {
2569
- resourceKind: ResourceEffectIdentityKind,
2570
- tag: identityTag,
2571
- create,
2572
- deps: createDeps,
2573
- inst,
2574
- // Circular
2575
- next: (null: any),
2576
- };
2577
- pushEffectImpl(effectIdentity);
2578
-
2579
- const effectUpdate: ResourceEffectUpdate = {
2580
- resourceKind: ResourceEffectUpdateKind,
2581
- tag: updateTag,
2582
- update,
2583
- deps: updateDeps,
2584
- inst,
2585
- identity: effectIdentity,
2586
- // Circular
2587
- next: (null: any),
2588
- };
2589
- return pushEffectImpl(effectUpdate);
2590
-}
2591
-
2511
function pushEffectImpl(effect: Effect): Effect {
2512
let componentUpdateQueue: null | FunctionComponentUpdateQueue =
2513
(currentlyRenderingFiber.updateQueue: any);
@@ -2609,7 +2528,7 @@ function pushEffectImpl(effect: Effect): Effect {
2528
}
2529
2530
function createEffectInstance(): EffectInstance {
2612
- return {destroy: undefined, resource: undefined};
2531
+ return {destroy: undefined};
2532
}
2533
2534
function mountRef<T>(initialValue: T): {current: T} {
@@ -2628,13 +2547,10 @@ function mountEffectImpl(
2547
fiberFlags: Flags,
2548
hookFlags: HookFlags,
2549
create: () => (() => void) | void,
2631
- createDeps: Array<mixed> | void | null,
2632
- update?: ((resource: {...} | void | null) => void) | void,
2633
- updateDeps?: Array<mixed> | void | null,
2634
- destroy?: ((resource: {...} | void | null) => void) | void,
2550
+ deps: Array<mixed> | void | null,
2551
): void {
2552
const hook = mountWorkInProgressHook();
2637
- const nextDeps = createDeps === undefined ? null : createDeps;
2553
+ const nextDeps = deps === undefined ? null : deps;
2554
currentlyRenderingFiber.flags |= fiberFlags;
2555
hook.memoizedState = pushSimpleEffect(
2556
HookHasEffect | hookFlags,
@@ -2685,223 +2601,35 @@ function updateEffectImpl(
2601
}
2602
2603
function mountEffect(
2688
- create: (() => (() => void) | void) | (() => {...} | void | null),
2689
- createDeps: Array<mixed> | void | null,
2690
- update?: ((resource: {...} | void | null) => void) | void,
2691
- updateDeps?: Array<mixed> | void | null,
2692
- destroy?: ((resource: {...} | void | null) => void) | void,
2604
+ create: () => (() => void) | void,
2605
+ deps: Array<mixed> | void | null,
2606
): void {
2607
if (
2608
__DEV__ &&
2609
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode &&
2610
(currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode
2611
) {
2699
- if (
2700
- enableUseEffectCRUDOverload &&
2701
- (typeof update === 'function' || typeof destroy === 'function')
2702
- ) {
2703
- mountResourceEffectImpl(
2704
- MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
2705
- HookPassive,
2706
- create,
2707
- createDeps,
2708
- update,
2709
- updateDeps,
2710
- destroy,
2711
- );
2712
- } else {
2713
- mountEffectImpl(
2714
- MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
2715
- HookPassive,
2716
- // $FlowFixMe[incompatible-call] @poteto it's not possible to narrow `create` without calling it.
2717
- create,
2718
- createDeps,
2719
- );
2720
- }
2721
- } else {
2722
- if (
2723
- enableUseEffectCRUDOverload &&
2724
- (typeof update === 'function' || typeof destroy === 'function')
2725
- ) {
2726
- mountResourceEffectImpl(
2727
- PassiveEffect | PassiveStaticEffect,
2728
- HookPassive,
2729
- create,
2730
- createDeps,
2731
- update,
2732
- updateDeps,
2733
- destroy,
2734
- );
2735
- } else {
2736
- mountEffectImpl(
2737
- PassiveEffect | PassiveStaticEffect,
2738
- HookPassive,
2739
- // $FlowFixMe[incompatible-call] @poteto it's not possible to narrow `create` without calling it.
2740
- create,
2741
- createDeps,
2742
- );
2743
- }
2744
- }
2745
-}
2746
-
2747
-function updateEffect(
2748
- create: (() => (() => void) | void) | (() => {...} | void | null),
2749
- createDeps: Array<mixed> | void | null,
2750
- update?: ((resource: {...} | void | null) => void) | void,
2751
- updateDeps?: Array<mixed> | void | null,
2752
- destroy?: ((resource: {...} | void | null) => void) | void,
2753
-): void {
2754
- if (
2755
- enableUseEffectCRUDOverload &&
2756
- (typeof update === 'function' || typeof destroy === 'function')
2757
- ) {
2758
- updateResourceEffectImpl(
2759
- PassiveEffect,
2612
+ mountEffectImpl(
2613
+ MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
2614
HookPassive,
2615
create,
2762
- createDeps,
2763
- update,
2764
- updateDeps,
2765
- destroy,
2616
+ deps,
2617
);
2618
} else {
2768
- // $FlowFixMe[incompatible-call] @poteto it's not possible to narrow `create` without calling it.
2769
- updateEffectImpl(PassiveEffect, HookPassive, create, createDeps);
2770
- }
2771
-}
2772
-
2773
-function mountResourceEffect(
2774
- create: () => {...} | void | null,
2775
- createDeps: Array<mixed> | void | null,
2776
- update: ((resource: {...} | void | null) => void) | void,
2777
- updateDeps: Array<mixed> | void | null,
2778
- destroy: ((resource: {...} | void | null) => void) | void,
2779
-) {
2780
- if (
2781
- __DEV__ &&
2782
- (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode &&
2783
- (currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode
2784
- ) {
2785
- } else {
2786
- mountResourceEffectImpl(
2619
+ mountEffectImpl(
2620
PassiveEffect | PassiveStaticEffect,
2621
HookPassive,
2622
create,
2790
- createDeps,
2791
- update,
2792
- updateDeps,
2793
- destroy,
2623
+ deps,
2624
);
2625
}
2626
}
2627
2798
-function mountResourceEffectImpl(
2799
- fiberFlags: Flags,
2800
- hookFlags: HookFlags,
2801
- create: () => {...} | void | null,
2802
- createDeps: Array<mixed> | void | null,
2803
- update: ((resource: {...} | void | null) => void) | void,
2804
- updateDeps: Array<mixed> | void | null,
2805
- destroy: ((resource: {...} | void | null) => void) | void,
2806
-) {
2807
- const hook = mountWorkInProgressHook();
2808
- currentlyRenderingFiber.flags |= fiberFlags;
2809
- const inst = createEffectInstance();
2810
- inst.destroy = destroy;
2811
- hook.memoizedState = pushResourceEffect(
2812
- HookHasEffect | hookFlags,
2813
- hookFlags,
2814
- inst,
2815
- create,
2816
- createDeps,
2817
- update,
2818
- updateDeps,
2819
- );
2820
-}
2821
-
2822
-function updateResourceEffect(
2823
- create: () => {...} | void | null,
2824
- createDeps: Array<mixed> | void | null,
2825
- update: ((resource: {...} | void | null) => void) | void,
2826
- updateDeps: Array<mixed> | void | null,
2827
- destroy: ((resource: {...} | void | null) => void) | void,
2828
-) {
2829
- updateResourceEffectImpl(
2830
- PassiveEffect,
2831
- HookPassive,
2832
- create,
2833
- createDeps,
2834
- update,
2835
- updateDeps,
2836
- destroy,
2837
- );
2838
-}
2839
-
2840
-function updateResourceEffectImpl(
2841
- fiberFlags: Flags,
2842
- hookFlags: HookFlags,
2843
- create: () => {...} | void | null,
2844
- createDeps: Array<mixed> | void | null,
2845
- update: ((resource: {...} | void | null) => void) | void,
2846
- updateDeps: Array<mixed> | void | null,
2847
- destroy: ((resource: {...} | void | null) => void) | void,
2848
-) {
2849
- const hook = updateWorkInProgressHook();
2850
- const effect: Effect = hook.memoizedState;
2851
- const inst = effect.inst;
2852
- inst.destroy = destroy;
2853
-
2854
- const nextCreateDeps = createDeps === undefined ? null : createDeps;
2855
- const nextUpdateDeps = updateDeps === undefined ? null : updateDeps;
2856
- let isCreateDepsSame: boolean;
2857
- let isUpdateDepsSame: boolean;
2858
-
2859
- if (currentHook !== null) {
2860
- const prevEffect: Effect = currentHook.memoizedState;
2861
- if (nextCreateDeps !== null) {
2862
- let prevCreateDeps;
2863
- if (
2864
- prevEffect.resourceKind != null &&
2865
- prevEffect.resourceKind === ResourceEffectUpdateKind
2866
- ) {
2867
- prevCreateDeps =
2868
- prevEffect.identity.deps != null ? prevEffect.identity.deps : null;
2869
- } else {
2870
- throw new Error(
2871
- `Expected a ResourceEffectUpdate to be pushed together with ResourceEffectIdentity. This is a bug in React.`,
2872
- );
2873
- }
2874
- isCreateDepsSame = areHookInputsEqual(nextCreateDeps, prevCreateDeps);
2875
- }
2876
- if (nextUpdateDeps !== null) {
2877
- let prevUpdateDeps;
2878
- if (
2879
- prevEffect.resourceKind != null &&
2880
- prevEffect.resourceKind === ResourceEffectUpdateKind
2881
- ) {
2882
- prevUpdateDeps = prevEffect.deps != null ? prevEffect.deps : null;
2883
- } else {
2884
- throw new Error(
2885
- `Expected a ResourceEffectUpdate to be pushed together with ResourceEffectIdentity. This is a bug in React.`,
2886
- );
2887
- }
2888
- isUpdateDepsSame = areHookInputsEqual(nextUpdateDeps, prevUpdateDeps);
2889
- }
2890
- }
2891
-
2892
- if (!(isCreateDepsSame && isUpdateDepsSame)) {
2893
- currentlyRenderingFiber.flags |= fiberFlags;
2894
- }
2895
-
2896
- hook.memoizedState = pushResourceEffect(
2897
- isCreateDepsSame ? hookFlags : HookHasEffect | hookFlags,
2898
- isUpdateDepsSame ? hookFlags : HookHasEffect | hookFlags,
2899
- inst,
2900
- create,
2901
- nextCreateDeps,
2902
- update,
2903
- nextUpdateDeps,
2904
- );
2628
+function updateEffect(
2629
+ create: () => (() => void) | void,
2630
+ deps: Array<mixed> | void | null,
2631
+): void {
2632
+ updateEffectImpl(PassiveEffect, HookPassive, create, deps);
2633
}
2634
2635
function useEffectEventImpl<Args, Return, F: (...Array<Args>) => Return>(
@@ -4339,30 +4067,13 @@ if (__DEV__) {
4067
return readContext(context);
4068
},
4069
useEffect(
4342
- create: (() => (() => void) | void) | (() => {...} | void | null),
4343
- createDeps: Array<mixed> | void | null,
4344
- update?: ((resource: {...} | void | null) => void) | void,
4345
- updateDeps?: Array<mixed> | void | null,
4346
- destroy?: ((resource: {...} | void | null) => void) | void,
4070
+ create: () => (() => void) | void,
4071
+ deps: Array<mixed> | void | null,
4072
): void {
4073
currentHookNameInDev = 'useEffect';
4074
mountHookTypesDev();
4350
- if (
4351
- enableUseEffectCRUDOverload &&
4352
- (typeof update === 'function' || typeof destroy === 'function')
4353
- ) {
4354
- checkDepsAreNonEmptyArrayDev(updateDeps);
4355
- return mountResourceEffect(
4356
- create,
4357
- createDeps,
4358
- update,
4359
- updateDeps,
4360
- destroy,
4361
- );
4362
- } else {
4363
- checkDepsAreArrayDev(createDeps);
4364
- return mountEffect(create, createDeps);
4365
- }
4075
+ checkDepsAreArrayDev(deps);
4076
+ return mountEffect(create, deps);
4077
},
4078
useImperativeHandle<T>(
4079
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
@@ -4540,28 +4251,12 @@ if (__DEV__) {
4251
return readContext(context);
4252
},
4253
useEffect(
4543
- create: (() => (() => void) | void) | (() => {...} | void | null),
4544
- createDeps: Array<mixed> | void | null,
4545
- update?: ((resource: {...} | void | null) => void) | void,
4546
- updateDeps?: Array<mixed> | void | null,
4547
- destroy?: ((resource: {...} | void | null) => void) | void,
4254
+ create: () => (() => void) | void,
4255
+ deps: Array<mixed> | void | null,
4256
): void {
4257
currentHookNameInDev = 'useEffect';
4258
updateHookTypesDev();
4551
- if (
4552
- enableUseEffectCRUDOverload &&
4553
- (typeof update === 'function' || typeof destroy === 'function')
4554
- ) {
4555
- return mountResourceEffect(
4556
- create,
4557
- createDeps,
4558
- update,
4559
- updateDeps,
4560
- destroy,
4561
- );
4562
- } else {
4563
- return mountEffect(create, createDeps);
4564
- }
4259
+ return mountEffect(create, deps);
4260
},
4261
useImperativeHandle<T>(
4262
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
@@ -4735,28 +4430,12 @@ if (__DEV__) {
4430
return readContext(context);
4431
},
4432
useEffect(
4738
- create: (() => (() => void) | void) | (() => {...} | void | null),
4739
- createDeps: Array<mixed> | void | null,
4740
- update?: ((resource: {...} | void | null) => void) | void,
4741
- updateDeps?: Array<mixed> | void | null,
4742
- destroy?: ((resource: {...} | void | null) => void) | void,
4433
+ create: () => (() => void) | void,
4434
+ deps: Array<mixed> | void | null,
4435
): void {
4436
currentHookNameInDev = 'useEffect';
4437
updateHookTypesDev();
4746
- if (
4747
- enableUseEffectCRUDOverload &&
4748
- (typeof update === 'function' || typeof destroy === 'function')
4749
- ) {
4750
- return updateResourceEffect(
4751
- create,
4752
- createDeps,
4753
- update,
4754
- updateDeps,
4755
- destroy,
4756
- );
4757
- } else {
4758
- return updateEffect(create, createDeps);
4759
- }
4438
+ return updateEffect(create, deps);
4439
},
4440
useImperativeHandle<T>(
4441
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
@@ -4930,28 +4609,12 @@ if (__DEV__) {
4609
return readContext(context);
4610
},
4611
useEffect(
4933
- create: (() => (() => void) | void) | (() => {...} | void | null),
4934
- createDeps: Array<mixed> | void | null,
4935
- update?: ((resource: {...} | void | null) => void) | void,
4936
- updateDeps?: Array<mixed> | void | null,
4937
- destroy?: ((resource: {...} | void | null) => void) | void,
4612
+ create: () => (() => void) | void,
4613
+ deps: Array<mixed> | void | null,
4614
): void {
4615
currentHookNameInDev = 'useEffect';
4616
updateHookTypesDev();
4941
- if (
4942
- enableUseEffectCRUDOverload &&
4943
- (typeof update === 'function' || typeof destroy === 'function')
4944
- ) {
4945
- return updateResourceEffect(
4946
- create,
4947
- createDeps,
4948
- update,
4949
- updateDeps,
4950
- destroy,
4951
- );
4952
- } else {
4953
- return updateEffect(create, createDeps);
4954
- }
4617
+ return updateEffect(create, deps);
4618
},
4619
useImperativeHandle<T>(
4620
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
@@ -5131,29 +4794,13 @@ if (__DEV__) {
4794
return readContext(context);
4795
},
4796
useEffect(
5134
- create: (() => (() => void) | void) | (() => {...} | void | null),
5135
- createDeps: Array<mixed> | void | null,
5136
- update?: ((resource: {...} | void | null) => void) | void,
5137
- updateDeps?: Array<mixed> | void | null,
5138
- destroy?: ((resource: {...} | void | null) => void) | void,
4797
+ create: () => (() => void) | void,
4798
+ deps: Array<mixed> | void | null,
4799
): void {
4800
currentHookNameInDev = 'useEffect';
4801
warnInvalidHookAccess();
4802
mountHookTypesDev();
5143
- if (
5144
- enableUseEffectCRUDOverload &&
5145
- (typeof update === 'function' || typeof destroy === 'function')
5146
- ) {
5147
- return mountResourceEffect(
5148
- create,
5149
- createDeps,
5150
- update,
5151
- updateDeps,
5152
- destroy,
5153
- );
5154
- } else {
5155
- return mountEffect(create, createDeps);
5156
- }
4803
+ return mountEffect(create, deps);
4804
},
4805
useImperativeHandle<T>(
4806
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
@@ -5352,29 +4999,13 @@ if (__DEV__) {
4999
return readContext(context);
5000
},
5001
useEffect(
5355
- create: (() => (() => void) | void) | (() => {...} | void | null),
5356
- createDeps: Array<mixed> | void | null,
5357
- update?: ((resource: {...} | void | null) => void) | void,
5358
- updateDeps?: Array<mixed> | void | null,
5359
- destroy?: ((resource: {...} | void | null) => void) | void,
5002
+ create: () => (() => void) | void,
5003
+ deps: Array<mixed> | void | null,
5004
): void {
5005
currentHookNameInDev = 'useEffect';
5006
warnInvalidHookAccess();
5007
updateHookTypesDev();
5364
- if (
5365
- enableUseEffectCRUDOverload &&
5366
- (typeof update === 'function' || typeof destroy === 'function')
5367
- ) {
5368
- return updateResourceEffect(
5369
- create,
5370
- createDeps,
5371
- update,
5372
- updateDeps,
5373
- destroy,
5374
- );
5375
- } else {
5376
- return updateEffect(create, createDeps);
5377
- }
5008
+ return updateEffect(create, deps);
5009
},
5010
useImperativeHandle<T>(
5011
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
@@ -5573,29 +5204,13 @@ if (__DEV__) {
5204
return readContext(context);
5205
},
5206
useEffect(
5576
- create: (() => (() => void) | void) | (() => {...} | void | null),
5577
- createDeps: Array<mixed> | void | null,
5578
- update?: ((resource: {...} | void | null) => void) | void,
5579
- updateDeps?: Array<mixed> | void | null,
5580
- destroy?: ((resource: {...} | void | null) => void) | void,
5207
+ create: () => (() => void) | void,
5208
+ deps: Array<mixed> | void | null,
5209
): void {
5210
currentHookNameInDev = 'useEffect';
5211
warnInvalidHookAccess();
5212
updateHookTypesDev();
5585
- if (
5586
- enableUseEffectCRUDOverload &&
5587
- (typeof update === 'function' || typeof destroy === 'function')
5588
- ) {
5589
- return updateResourceEffect(
5590
- create,
5591
- createDeps,
5592
- update,
5593
- updateDeps,
5594
- destroy,
5595
- );
5596
- } else {
5597
- return updateEffect(create, createDeps);
5598
- }
5213
+ return updateEffect(create, deps);
5214
},
5215
useImperativeHandle<T>(
5216
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
packages/react-reconciler/src/ReactInternalTypes.js
+2
-5
@@ -399,11 +399,8 @@ export type Dispatcher = {
399
useContext<T>(context: ReactContext<T>): T,
400
useRef<T>(initialValue: T): {current: T},
401
useEffect(
402
- create: (() => (() => void) | void) | (() => {...} | void | null),
403
- createDeps: Array<mixed> | void | null,
404
- update?: ((resource: {...} | void | null) => void) | void,
405
- updateDeps?: Array<mixed> | void | null,
406
- destroy?: ((resource: {...} | void | null) => void) | void,
402
+ create: () => (() => void) | void,
403
+ deps: Array<mixed> | void | null,
404
): void,
405
// TODO: Non-nullable once `enableUseEffectEventHook` is on everywhere.
406
useEffectEvent?: <Args, F: (...Array<Args>) => mixed>(callback: F) => F,
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
-754
@@ -3309,760 +3309,6 @@ describe('ReactHooksWithNoopRenderer', () => {
3309
});
3310
});
3311
3312
- // @gate enableUseEffectCRUDOverload
3313
- describe('useEffect CRUD overload', () => {
3314
- class Resource {
3315
- isDeleted: false;
3316
- id: string;
3317
- opts: mixed;
3318
- constructor(id, opts) {
3319
- this.id = id;
3320
- this.opts = opts;
3321
- }
3322
- update(opts) {
3323
- if (this.isDeleted) {
3324
- console.error('Cannot update deleted resource');
3325
- return;
3326
- }
3327
- this.opts = opts;
3328
- }
3329
- destroy() {
3330
- this.isDeleted = true;
3331
- }
3332
- }
3333
-
3334
- // @gate !enableUseEffectCRUDOverload
3335
- it('is null when flag is disabled', async () => {
3336
- function App({id}) {
3337
- useEffect(
3338
- () => {
3339
- Scheduler.log(`create(${id})`);
3340
- return {};
3341
- },
3342
- [id],
3343
- () => {
3344
- Scheduler.log('update');
3345
- },
3346
- [],
3347
- );
3348
- return null;
3349
- }
3350
-
3351
- await expect(async () => {
3352
- await act(() => {
3353
- ReactNoop.render(<App id={1} />);
3354
- });
3355
- }).rejects.toThrow(
3356
- 'useEffect CRUD overload is not enabled in this build of React.',
3357
- );
3358
- });
3359
-
3360
- // @gate enableUseEffectCRUDOverload
3361
- it('validates non-empty update deps', async () => {
3362
- function App({id}) {
3363
- useEffect(
3364
- () => {
3365
- Scheduler.log(`create(${id})`);
3366
- return {};
3367
- },
3368
- [id],
3369
- () => {
3370
- Scheduler.log('update');
3371
- },
3372
- [],
3373
- );
3374
- return null;
3375
- }
3376
-
3377
- await act(() => {
3378
- ReactNoop.render(<App id={1} />);
3379
- });
3380
- assertConsoleErrorDev([
3381
- 'useEffect received a dependency array with no dependencies. ' +
3382
- 'When specified, the dependency array must have at least one dependency.\n' +
3383
- ' in App (at **)',
3384
- ]);
3385
- });
3386
-
3387
- // @gate enableUseEffectCRUDOverload
3388
- it('simple mount and update', async () => {
3389
- function App({id, username}) {
3390
- const opts = useMemo(() => {
3391
- return {username};
3392
- }, [username]);
3393
- useEffect(
3394
- () => {
3395
- const resource = new Resource(id, opts);
3396
- Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
3397
- return resource;
3398
- },
3399
- [id],
3400
- resource => {
3401
- resource.update(opts);
3402
- Scheduler.log(`update(${resource.id}, ${resource.opts.username})`);
3403
- },
3404
- [opts],
3405
- resource => {
3406
- resource.destroy();
3407
- Scheduler.log(`destroy(${resource.id}, ${resource.opts.username})`);
3408
- },
3409
- );
3410
- return null;
3411
- }
3412
-
3413
- await act(() => {
3414
- ReactNoop.render(<App id={1} username="Jack" />);
3415
- });
3416
- assertLog(['create(1, Jack)']);
3417
-
3418
- await act(() => {
3419
- ReactNoop.render(<App id={1} username="Lauren" />);
3420
- });
3421
- assertLog(['update(1, Lauren)']);
3422
-
3423
- await act(() => {
3424
- ReactNoop.render(<App id={1} username="Lauren" />);
3425
- });
3426
- assertLog([]);
3427
-
3428
- await act(() => {
3429
- ReactNoop.render(<App id={1} username="Jordan" />);
3430
- });
3431
- assertLog(['update(1, Jordan)']);
3432
-
3433
- await act(() => {
3434
- ReactNoop.render(<App id={2} username="Jack" />);
3435
- });
3436
- assertLog(['destroy(1, Jordan)', 'create(2, Jack)']);
3437
-
3438
- await act(() => {
3439
- ReactNoop.render(null);
3440
- });
3441
- assertLog(['destroy(2, Jack)']);
3442
- });
3443
-
3444
- // @gate enableUseEffectCRUDOverload
3445
- it('simple mount with no update', async () => {
3446
- function App({id, username}) {
3447
- const opts = useMemo(() => {
3448
- return {username};
3449
- }, [username]);
3450
- useEffect(
3451
- () => {
3452
- const resource = new Resource(id, opts);
3453
- Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
3454
- return resource;
3455
- },
3456
- [id],
3457
- resource => {
3458
- resource.update(opts);
3459
- Scheduler.log(`update(${resource.id}, ${resource.opts.username})`);
3460
- },
3461
- [opts],
3462
- resource => {
3463
- resource.destroy();
3464
- Scheduler.log(`destroy(${resource.id}, ${resource.opts.username})`);
3465
- },
3466
- );
3467
- return null;
3468
- }
3469
-
3470
- await act(() => {
3471
- ReactNoop.render(<App id={1} username="Jack" />);
3472
- });
3473
- assertLog(['create(1, Jack)']);
3474
-
3475
- await act(() => {
3476
- ReactNoop.render(null);
3477
- });
3478
- assertLog(['destroy(1, Jack)']);
3479
- });
3480
-
3481
- // @gate enableUseEffectCRUDOverload
3482
- it('calls update on every render if no deps are specified', async () => {
3483
- function App({id, username}) {
3484
- const opts = useMemo(() => {
3485
- return {username};
3486
- }, [username]);
3487
- useEffect(
3488
- () => {
3489
- const resource = new Resource(id, opts);
3490
- Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
3491
- return resource;
3492
- },
3493
- [id],
3494
- resource => {
3495
- resource.update(opts);
3496
- Scheduler.log(`update(${resource.id}, ${resource.opts.username})`);
3497
- },
3498
- );
3499
- return null;
3500
- }
3501
-
3502
- await act(() => {
3503
- ReactNoop.render(<App id={1} username="Jack" />);
3504
- });
3505
- assertLog(['create(1, Jack)']);
3506
-
3507
- await act(() => {
3508
- ReactNoop.render(<App id={1} username="Jack" />);
3509
- });
3510
- assertLog(['update(1, Jack)']);
3511
-
3512
- await act(() => {
3513
- ReactNoop.render(<App id={2} username="Jack" />);
3514
- });
3515
- assertLog(['create(2, Jack)', 'update(2, Jack)']);
3516
-
3517
- await act(() => {
3518
- ReactNoop.render(<App id={2} username="Lauren" />);
3519
- });
3520
-
3521
- assertLog(['update(2, Lauren)']);
3522
- });
3523
-
3524
- // @gate enableUseEffectCRUDOverload
3525
- it('does not unmount previous useEffect between updates', async () => {
3526
- function App({id}) {
3527
- useEffect(
3528
- () => {
3529
- const resource = new Resource(id);
3530
- Scheduler.log(`create(${resource.id})`);
3531
- return resource;
3532
- },
3533
- [],
3534
- resource => {
3535
- Scheduler.log(`update(${resource.id})`);
3536
- },
3537
- undefined,
3538
- resource => {
3539
- Scheduler.log(`destroy(${resource.id})`);
3540
- resource.destroy();
3541
- },
3542
- );
3543
- return <Text text={'Id: ' + id} />;
3544
- }
3545
-
3546
- await act(async () => {
3547
- ReactNoop.render(<App id={0} />, () => Scheduler.log('Sync effect'));
3548
- await waitFor(['Id: 0', 'Sync effect']);
3549
- expect(ReactNoop).toMatchRenderedOutput(<span prop="Id: 0" />);
3550
- });
3551
-
3552
- assertLog(['create(0)']);
3553
-
3554
- await act(async () => {
3555
- ReactNoop.render(<App id={1} />, () => Scheduler.log('Sync effect'));
3556
- await waitFor(['Id: 1', 'Sync effect']);
3557
- expect(ReactNoop).toMatchRenderedOutput(<span prop="Id: 1" />);
3558
- });
3559
-
3560
- assertLog(['update(0)']);
3561
- });
3562
-
3563
- // @gate enableUseEffectCRUDOverload
3564
- it('unmounts only on deletion', async () => {
3565
- function App({id}) {
3566
- useEffect(
3567
- () => {
3568
- const resource = new Resource(id);
3569
- Scheduler.log(`create(${resource.id})`);
3570
- return resource;
3571
- },
3572
- undefined,
3573
- resource => {
3574
- Scheduler.log(`update(${resource.id})`);
3575
- },
3576
- undefined,
3577
- resource => {
3578
- Scheduler.log(`destroy(${resource.id})`);
3579
- resource.destroy();
3580
- },
3581
- );
3582
- return <Text text={'Id: ' + id} />;
3583
- }
3584
- await act(async () => {
3585
- ReactNoop.render(<App id={0} />, () => Scheduler.log('Sync effect'));
3586
- await waitFor(['Id: 0', 'Sync effect']);
3587
- expect(ReactNoop).toMatchRenderedOutput(<span prop="Id: 0" />);
3588
- });
3589
-
3590
- assertLog(['create(0)']);
3591
-
3592
- ReactNoop.render(null);
3593
- await waitForAll(['destroy(0)']);
3594
- expect(ReactNoop).toMatchRenderedOutput(null);
3595
- });
3596
-
3597
- // @gate enableUseEffectCRUDOverload
3598
- it('unmounts on deletion', async () => {
3599
- function Wrapper(props) {
3600
- return <App {...props} />;
3601
- }
3602
- function App({id, username}) {
3603
- const opts = useMemo(() => {
3604
- return {username};
3605
- }, [username]);
3606
- useEffect(
3607
- () => {
3608
- const resource = new Resource(id, opts);
3609
- Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
3610
- return resource;
3611
- },
3612
- [id],
3613
- resource => {
3614
- resource.update(opts);
3615
- Scheduler.log(`update(${resource.id}, ${resource.opts.username})`);
3616
- },
3617
- [opts],
3618
- resource => {
3619
- resource.destroy();
3620
- Scheduler.log(`destroy(${resource.id}, ${resource.opts.username})`);
3621
- },
3622
- );
3623
- return <Text text={'Id: ' + id} />;
3624
- }
3625
-
3626
- await act(async () => {
3627
- ReactNoop.render(<Wrapper id={0} username="Sathya" />, () =>
3628
- Scheduler.log('Sync effect'),
3629
- );
3630
- await waitFor(['Id: 0', 'Sync effect']);
3631
- expect(ReactNoop).toMatchRenderedOutput(<span prop="Id: 0" />);
3632
- });
3633
-
3634
- assertLog(['create(0, Sathya)']);
3635
-
3636
- await act(async () => {
3637
- ReactNoop.render(<Wrapper id={0} username="Lauren" />, () =>
3638
- Scheduler.log('Sync effect'),
3639
- );
3640
- await waitFor(['Id: 0', 'Sync effect']);
3641
- expect(ReactNoop).toMatchRenderedOutput(<span prop="Id: 0" />);
3642
- });
3643
-
3644
- assertLog(['update(0, Lauren)']);
3645
-
3646
- ReactNoop.render(null);
3647
- await waitForAll(['destroy(0, Lauren)']);
3648
- expect(ReactNoop).toMatchRenderedOutput(null);
3649
- });
3650
-
3651
- // @gate enableUseEffectCRUDOverload
3652
- it('handles errors in create on mount', async () => {
3653
- function App({id}) {
3654
- useEffect(
3655
- () => {
3656
- Scheduler.log(`Mount A [${id}]`);
3657
- return {};
3658
- },
3659
- undefined,
3660
- undefined,
3661
- undefined,
3662
- resource => {
3663
- Scheduler.log(`Unmount A [${id}]`);
3664
- },
3665
- );
3666
- useEffect(
3667
- () => {
3668
- Scheduler.log('Oops!');
3669
- throw new Error('Oops!');
3670
- // eslint-disable-next-line no-unreachable
3671
- Scheduler.log(`Mount B [${id}]`);
3672
- return {};
3673
- },
3674
- undefined,
3675
- undefined,
3676
- undefined,
3677
- resource => {
3678
- Scheduler.log(`Unmount B [${id}]`);
3679
- },
3680
- );
3681
- return <Text text={'Id: ' + id} />;
3682
- }
3683
- await expect(async () => {
3684
- await act(async () => {
3685
- ReactNoop.render(<App id={0} />, () => Scheduler.log('Sync effect'));
3686
- await waitFor(['Id: 0', 'Sync effect']);
3687
- expect(ReactNoop).toMatchRenderedOutput(<span prop="Id: 0" />);
3688
- });
3689
- }).rejects.toThrow('Oops');
3690
-
3691
- assertLog([
3692
- 'Mount A [0]',
3693
- 'Oops!',
3694
- // Clean up effect A. There's no effect B to clean-up, because it
3695
- // never mounted.
3696
- 'Unmount A [0]',
3697
- ]);
3698
- expect(ReactNoop).toMatchRenderedOutput(null);
3699
- });
3700
-
3701
- // @gate enableUseEffectCRUDOverload
3702
- it('handles errors in create on update', async () => {
3703
- function App({id}) {
3704
- useEffect(
3705
- () => {
3706
- Scheduler.log(`Mount A [${id}]`);
3707
- return {};
3708
- },
3709
- [],
3710
- () => {
3711
- if (id === 1) {
3712
- Scheduler.log('Oops!');
3713
- throw new Error('Oops error!');
3714
- }
3715
- Scheduler.log(`Update A [${id}]`);
3716
- },
3717
- [id],
3718
- () => {
3719
- Scheduler.log(`Unmount A [${id}]`);
3720
- },
3721
- );
3722
- return <Text text={'Id: ' + id} />;
3723
- }
3724
- await act(async () => {
3725
- ReactNoop.render(<App id={0} />, () => Scheduler.log('Sync effect'));
3726
- await waitFor(['Id: 0', 'Sync effect']);
3727
- expect(ReactNoop).toMatchRenderedOutput(<span prop="Id: 0" />);
3728
- ReactNoop.flushPassiveEffects();
3729
- assertLog(['Mount A [0]']);
3730
- });
3731
-
3732
- await expect(async () => {
3733
- await act(async () => {
3734
- // This update will trigger an error
3735
- ReactNoop.render(<App id={1} />, () => Scheduler.log('Sync effect'));
3736
- await waitFor(['Id: 1', 'Sync effect']);
3737
- expect(ReactNoop).toMatchRenderedOutput(<span prop="Id: 1" />);
3738
- ReactNoop.flushPassiveEffects();
3739
- assertLog(['Oops!', 'Unmount A [1]']);
3740
- expect(ReactNoop).toMatchRenderedOutput(null);
3741
- });
3742
- }).rejects.toThrow('Oops error!');
3743
- });
3744
-
3745
- // @gate enableUseEffectCRUDOverload
3746
- it('handles errors in destroy on update', async () => {
3747
- function App({id, username}) {
3748
- const opts = useMemo(() => {
3749
- return {username};
3750
- }, [username]);
3751
- useEffect(
3752
- () => {
3753
- const resource = new Resource(id, opts);
3754
- Scheduler.log(`Mount A [${id}, ${resource.opts.username}]`);
3755
- return resource;
3756
- },
3757
- [id],
3758
- resource => {
3759
- resource.update(opts);
3760
- Scheduler.log(`Update A [${id}, ${resource.opts.username}]`);
3761
- },
3762
- [opts],
3763
- resource => {
3764
- Scheduler.log(`Oops, ${resource.opts.username}!`);
3765
- if (id === 1) {
3766
- throw new Error(`Oops ${resource.opts.username} error!`);
3767
- }
3768
- Scheduler.log(`Unmount A [${id}, ${resource.opts.username}]`);
3769
- },
3770
- );
3771
- return <Text text={'Id: ' + id} />;
3772
- }
3773
- await act(async () => {
3774
- ReactNoop.render(<App id={0} username="Lauren" />, () =>
3775
- Scheduler.log('Sync effect'),
3776
- );
3777
- await waitFor(['Id: 0', 'Sync effect']);
3778
- expect(ReactNoop).toMatchRenderedOutput(<span prop="Id: 0" />);
3779
- ReactNoop.flushPassiveEffects();
3780
- assertLog(['Mount A [0, Lauren]']);
3781
- });
3782
-
3783
- await expect(async () => {
3784
- await act(async () => {
3785
- // This update will trigger an error during passive effect unmount
3786
- ReactNoop.render(<App id={1} username="Sathya" />, () =>
3787
- Scheduler.log('Sync effect'),
3788
- );
3789
- await waitFor(['Id: 1', 'Sync effect']);
3790
- expect(ReactNoop).toMatchRenderedOutput(<span prop="Id: 1" />);
3791
- ReactNoop.flushPassiveEffects();
3792
- assertLog(['Oops, Lauren!', 'Mount A [1, Sathya]', 'Oops, Sathya!']);
3793
- });
3794
- // TODO(lauren) more explicit assertions. this is weird because we
3795
- // destroy both the first and second resource
3796
- }).rejects.toThrow();
3797
-
3798
- expect(ReactNoop).toMatchRenderedOutput(null);
3799
- });
3800
-
3801
- // @gate enableUseEffectCRUDOverload && enableActivity
3802
- it('composes with activity', async () => {
3803
- function App({id, username}) {
3804
- const opts = useMemo(() => {
3805
- return {username};
3806
- }, [username]);
3807
- useEffect(
3808
- () => {
3809
- const resource = new Resource(id, opts);
3810
- Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
3811
- return resource;
3812
- },
3813
- [id],
3814
- resource => {
3815
- resource.update(opts);
3816
- Scheduler.log(`update(${resource.id}, ${resource.opts.username})`);
3817
- },
3818
- [opts],
3819
- resource => {
3820
- resource.destroy();
3821
- Scheduler.log(`destroy(${resource.id}, ${resource.opts.username})`);
3822
- },
3823
- );
3824
- return null;
3825
- }
3826
-
3827
- const root = ReactNoop.createRoot();
3828
- await act(() => {
3829
- root.render(
3830
- <Activity mode="hidden">
3831
- <App id={0} username="Rick" />
3832
- </Activity>,
3833
- );
3834
- });
3835
- assertLog([]);
3836
-
3837
- await act(() => {
3838
- root.render(
3839
- <Activity mode="hidden">
3840
- <App id={0} username="Lauren" />
3841
- </Activity>,
3842
- );
3843
- });
3844
- assertLog([]);
3845
-
3846
- await act(() => {
3847
- root.render(
3848
- <Activity mode="visible">
3849
- <App id={0} username="Rick" />
3850
- </Activity>,
3851
- );
3852
- });
3853
- assertLog(['create(0, Rick)']);
3854
-
3855
- await act(() => {
3856
- root.render(
3857
- <Activity mode="visible">
3858
- <App id={0} username="Lauren" />
3859
- </Activity>,
3860
- );
3861
- });
3862
- assertLog(['update(0, Lauren)']);
3863
-
3864
- await act(() => {
3865
- root.render(
3866
- <Activity mode="hidden">
3867
- <App id={0} username="Lauren" />
3868
- </Activity>,
3869
- );
3870
- });
3871
- assertLog(['destroy(0, Lauren)']);
3872
- });
3873
-
3874
- // @gate enableUseEffectCRUDOverload
3875
- it('composes with suspense', async () => {
3876
- function TextBox({text}) {
3877
- return <AsyncText text={text} ms={0} />;
3878
- }
3879
- let setUsername_;
3880
- function App({id}) {
3881
- const [username, setUsername] = useState('Mofei');
3882
- setUsername_ = setUsername;
3883
- const opts = useMemo(() => {
3884
- return {username};
3885
- }, [username]);
3886
- useEffect(
3887
- () => {
3888
- const resource = new Resource(id, opts);
3889
- Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
3890
- return resource;
3891
- },
3892
- [id],
3893
- resource => {
3894
- resource.update(opts);
3895
- Scheduler.log(`update(${resource.id}, ${resource.opts.username})`);
3896
- },
3897
- [opts],
3898
- resource => {
3899
- resource.destroy();
3900
- Scheduler.log(`destroy(${resource.id}, ${resource.opts.username})`);
3901
- },
3902
- );
3903
- return (
3904
- <>
3905
- <Text text={'Sync: ' + username} />
3906
- <Suspense fallback={<Text text={'Loading'} />}>
3907
- <TextBox text={username} />
3908
- </Suspense>
3909
- </>
3910
- );
3911
- }
3912
-
3913
- await act(async () => {
3914
- ReactNoop.render(<App id={0} />);
3915
- await waitFor([
3916
- 'Sync: Mofei',
3917
- 'Suspend! [Mofei]',
3918
- 'Loading',
3919
- 'create(0, Mofei)',
3920
- ]);
3921
- expect(ReactNoop).toMatchRenderedOutput(
3922
- <>
3923
- <span prop="Sync: Mofei" />
3924
- <span prop="Loading" />
3925
- </>,
3926
- );
3927
- ReactNoop.flushPassiveEffects();
3928
- assertLog([]);
3929
-
3930
- Scheduler.unstable_advanceTime(10);
3931
- await advanceTimers(10);
3932
- assertLog(['Promise resolved [Mofei]']);
3933
- });
3934
- assertLog(['Mofei']);
3935
- expect(ReactNoop).toMatchRenderedOutput(
3936
- <>
3937
- <span prop="Sync: Mofei" />
3938
- <span prop="Mofei" />
3939
- </>,
3940
- );
3941
-
3942
- await act(async () => {
3943
- ReactNoop.render(<App id={1} />, () => Scheduler.log('Sync effect'));
3944
- await waitFor([
3945
- 'Sync: Mofei',
3946
- 'Mofei',
3947
- 'Sync effect',
3948
- 'destroy(0, Mofei)',
3949
- 'create(1, Mofei)',
3950
- ]);
3951
- expect(ReactNoop).toMatchRenderedOutput(
3952
- <>
3953
- <span prop="Sync: Mofei" />
3954
- <span prop="Mofei" />
3955
- </>,
3956
- );
3957
- ReactNoop.flushPassiveEffects();
3958
- assertLog([]);
3959
- });
3960
-
3961
- await act(async () => {
3962
- setUsername_('Lauren');
3963
- await waitFor([
3964
- 'Sync: Lauren',
3965
- 'Suspend! [Lauren]',
3966
- 'Loading',
3967
- 'update(1, Lauren)',
3968
- ]);
3969
- expect(ReactNoop).toMatchRenderedOutput(
3970
- <>
3971
- <span prop="Sync: Lauren" />
3972
- <span hidden={true} prop="Mofei" />
3973
- <span prop="Loading" />
3974
- </>,
3975
- );
3976
- ReactNoop.flushPassiveEffects();
3977
- assertLog([]);
3978
-
3979
- Scheduler.unstable_advanceTime(10);
3980
- await advanceTimers(10);
3981
- assertLog(['Promise resolved [Lauren]']);
3982
- });
3983
- assertLog(['Lauren']);
3984
- expect(ReactNoop).toMatchRenderedOutput(
3985
- <>
3986
- <span prop="Sync: Lauren" />
3987
- <span prop="Lauren" />
3988
- </>,
3989
- );
3990
- });
3991
-
3992
- // @gate enableUseEffectCRUDOverload
3993
- it('composes with other kinds of effects', async () => {
3994
- let rerender;
3995
- function App({id, username}) {
3996
- const [count, rerender_] = useState(0);
3997
- rerender = rerender_;
3998
- const opts = useMemo(() => {
3999
- return {username};
4000
- }, [username]);
4001
- useEffect(() => {
4002
- Scheduler.log(`useEffect(${count})`);
4003
- }, [count]);
4004
- useEffect(
4005
- () => {
4006
- const resource = new Resource(id, opts);
4007
- Scheduler.log(`create(${resource.id}, ${resource.opts.username})`);
4008
- return resource;
4009
- },
4010
- [id],
4011
- resource => {
4012
- resource.update(opts);
4013
- Scheduler.log(`update(${resource.id}, ${resource.opts.username})`);
4014
- },
4015
- [opts],
4016
- resource => {
4017
- resource.destroy();
4018
- Scheduler.log(`destroy(${resource.id}, ${resource.opts.username})`);
4019
- },
4020
- );
4021
- return null;
4022
- }
4023
-
4024
- await act(() => {
4025
- ReactNoop.render(<App id={1} username="Jack" />);
4026
- });
4027
- assertLog(['useEffect(0)', 'create(1, Jack)']);
4028
-
4029
- await act(() => {
4030
- ReactNoop.render(<App id={1} username="Lauren" />);
4031
- });
4032
- assertLog(['update(1, Lauren)']);
4033
-
4034
- await act(() => {
4035
- ReactNoop.render(<App id={1} username="Lauren" />);
4036
- });
4037
- assertLog([]);
4038
-
4039
- await act(() => {
4040
- ReactNoop.render(<App id={1} username="Jordan" />);
4041
- });
4042
- assertLog(['update(1, Jordan)']);
4043
-
4044
- await act(() => {
4045
- rerender(n => n + 1);
4046
- });
4047
- assertLog(['useEffect(1)']);
4048
-
4049
- await act(() => {
4050
- ReactNoop.render(<App id={1} username="Mofei" />);
4051
- });
4052
- assertLog(['update(1, Mofei)']);
4053
-
4054
- await act(() => {
4055
- ReactNoop.render(<App id={2} username="Jack" />);
4056
- });
4057
- assertLog(['destroy(1, Mofei)', 'create(2, Jack)']);
4058
-
4059
- await act(() => {
4060
- ReactNoop.render(null);
4061
- });
4062
- assertLog(['destroy(2, Jack)']);
4063
- });
4064
- });
4065
-
3312
describe('useCallback', () => {
3313
it('memoizes callback by comparing inputs', async () => {
3314
class IncrementButton extends React.PureComponent {
packages/react/src/ReactHooks.js
+4
-27
@@ -19,10 +19,7 @@ import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols';
19
20
import ReactSharedInternals from 'shared/ReactSharedInternals';
21
22
-import {
23
- enableUseEffectCRUDOverload,
24
- enableSwipeTransition,
25
-} from 'shared/ReactFeatureFlags';
22
+import {enableSwipeTransition} from 'shared/ReactFeatureFlags';
23
24
type BasicStateAction<S> = (S => S) | S;
25
type Dispatch<A> = A => void;
@@ -91,11 +88,8 @@ export function useRef<T>(initialValue: T): {current: T} {
88
}
89
90
export function useEffect(
94
- create: (() => (() => void) | void) | (() => {...} | void | null),
95
- createDeps: Array<mixed> | void | null,
96
- update?: ((resource: {...} | void | null) => void) | void,
97
- updateDeps?: Array<mixed> | void | null,
98
- destroy?: ((resource: {...} | void | null) => void) | void,
91
+ create: () => (() => void) | void,
92
+ deps: Array<mixed> | void | null,
93
): void {
94
if (__DEV__) {
95
if (create == null) {
@@ -106,24 +100,7 @@ export function useEffect(
100
}
101
102
const dispatcher = resolveDispatcher();
109
- if (
110
- enableUseEffectCRUDOverload &&
111
- (typeof update === 'function' || typeof destroy === 'function')
112
- ) {
113
- // $FlowFixMe[not-a-function] This is unstable, thus optional
114
- return dispatcher.useEffect(
115
- create,
116
- createDeps,
117
- update,
118
- updateDeps,
119
- destroy,
120
- );
121
- } else if (typeof update === 'function') {
122
- throw new Error(
123
- 'useEffect CRUD overload is not enabled in this build of React.',
124
- );
125
- }
126
- return dispatcher.useEffect(create, createDeps);
103
+ return dispatcher.useEffect(create, deps);
104
}
105
106
export function useInsertionEffect(
packages/shared/ReactFeatureFlags.js
-5
@@ -154,11 +154,6 @@ export const transitionLaneExpirationMs = 5000;
154
*/
155
export const enableInfiniteRenderLoopDetection = false;
156
157
-/**
158
- * Experimental new hook for better managing resources in effects.
159
- */
160
-export const enableUseEffectCRUDOverload = false;
161
-
157
export const enableFastAddPropertiesInDiffing = true;
158
export const enableLazyPublicInstanceInFabric = false;
159
packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js
-1
@@ -25,7 +25,6 @@ export const enableShallowPropDiffing = __VARIANT__;
25
export const passChildrenWhenCloningPersistedNodes = __VARIANT__;
26
export const enableFabricCompleteRootInCommitPhase = __VARIANT__;
27
export const enableSiblingPrerendering = __VARIANT__;
28
-export const enableUseEffectCRUDOverload = __VARIANT__;
28
export const enableFastAddPropertiesInDiffing = __VARIANT__;
29
export const enableLazyPublicInstanceInFabric = __VARIANT__;
30
export const renameElementSymbol = __VARIANT__;
packages/shared/forks/ReactFeatureFlags.native-fb.js
-1
@@ -25,7 +25,6 @@ export const {
25
enableObjectFiber,
26
enablePersistedModeClonedFlag,
27
enableShallowPropDiffing,
28
- enableUseEffectCRUDOverload,
28
passChildrenWhenCloningPersistedNodes,
29
enableSiblingPrerendering,
30
enableFastAddPropertiesInDiffing,
packages/shared/forks/ReactFeatureFlags.native-oss.js
-1
@@ -63,7 +63,6 @@ export const retryLaneExpirationMs = 5000;
63
export const syncLaneExpirationMs = 250;
64
export const transitionLaneExpirationMs = 5000;
65
export const enableSiblingPrerendering = true;
66
-export const enableUseEffectCRUDOverload = false;
66
67
export const enableHydrationLaneScheduling = true;
68
packages/shared/forks/ReactFeatureFlags.test-renderer.js
-2
@@ -65,8 +65,6 @@ export const renameElementSymbol = true;
65
export const enableShallowPropDiffing = false;
66
export const enableSiblingPrerendering = true;
67
68
-export const enableUseEffectCRUDOverload = false;
69
-
68
export const enableYieldingBeforePassive = true;
69
70
export const enableThrottledScheduling = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
-1
@@ -62,7 +62,6 @@ export const syncLaneExpirationMs = 250;
62
export const transitionLaneExpirationMs = 5000;
63
export const enableFabricCompleteRootInCommitPhase = false;
64
export const enableSiblingPrerendering = true;
65
-export const enableUseEffectCRUDOverload = true;
65
export const enableHydrationLaneScheduling = true;
66
export const enableYieldingBeforePassive = false;
67
export const enableThrottledScheduling = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
-2
@@ -74,8 +74,6 @@ export const enableObjectFiber = false;
74
export const enableShallowPropDiffing = false;
75
export const enableSiblingPrerendering = true;
76
77
-export const enableUseEffectCRUDOverload = false;
78
-
77
export const enableHydrationLaneScheduling = true;
78
79
export const enableYieldingBeforePassive = false;
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
-1
@@ -35,7 +35,6 @@ export const enableSchedulingProfiler = __VARIANT__;
35
export const enableInfiniteRenderLoopDetection = __VARIANT__;
36
export const enableSiblingPrerendering = __VARIANT__;
37
38
-export const enableUseEffectCRUDOverload = __VARIANT__;
38
export const enableFastAddPropertiesInDiffing = __VARIANT__;
39
export const enableLazyPublicInstanceInFabric = false;
40
export const enableViewTransition = __VARIANT__;
packages/shared/forks/ReactFeatureFlags.www.js
-1
@@ -29,7 +29,6 @@ export const {
29
enableSiblingPrerendering,
30
enableTransitionTracing,
31
enableTrustedTypesIntegration,
32
- enableUseEffectCRUDOverload,
32
favorSafetyOverHydrationPerf,
33
renameElementSymbol,
34
retryLaneExpirationMs,