@samitouri / QOS-React / commits / 313332d111

[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,