@samitouri / QOS-React-2 / commits / 3957853ae5

Re-add "strict effects mode" for legacy roots only (#20639)

This combines changes originally made in #19523, #20028, and #20415 but with slightly different semantics: "strict effects" mode is enabled only for the experimental root APIs (never for legacy render, regardless of <StrictMode> usage). These semantics may change slightly in the future.

Brian Vaughn committed Jan 22, 2021 at 05:58 UTC 3957853ae5095a85bacf6d414ee200e8236e9ed4
10 files changed +707 -102
packages/react-reconciler/src/ReactFiberClassComponent.new.js
+49 -6
@@ -12,13 +12,14 @@ import type {Lanes} from './ReactFiberLane.new';
12 import type {UpdateQueue} from './ReactUpdateQueue.new';
13
14 import * as React from 'react';
15 -import {Update, Snapshot} from './ReactFiberFlags';
15 +import {MountLayoutDev, Update, Snapshot} from './ReactFiberFlags';
16 import {
17 debugRenderPhaseSideEffectsForStrictMode,
18 disableLegacyContext,
19 enableDebugTracing,
20 enableSchedulingProfiler,
21 warnAboutDeprecatedLifecycles,
22 + enableDoubleInvokingEffects,
23 } from 'shared/ReactFeatureFlags';
24 import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
25 import {isMounted} from './ReactFiberTreeReflection';
@@ -29,7 +30,13 @@ import invariant from 'shared/invariant';
30 import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
31
32 import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
32 -import {DebugTracingMode, StrictMode} from './ReactTypeOfMode';
33 +import {
34 + BlockingMode,
35 + ConcurrentMode,
36 + DebugTracingMode,
37 + NoMode,
38 + StrictMode,
39 +} from './ReactTypeOfMode';
40
41 import {
42 enqueueUpdate,
@@ -890,7 +897,16 @@ function mountClassInstance(
897 }
898
899 if (typeof instance.componentDidMount === 'function') {
893 - workInProgress.flags |= Update;
900 + if (
901 + __DEV__ &&
902 + enableDoubleInvokingEffects &&
903 + (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
904 + ) {
905 + // Never double-invoke effects for legacy roots.
906 + workInProgress.flags |= MountLayoutDev | Update;
907 + } else {
908 + workInProgress.flags |= Update;
909 + }
910 }
911 }
912
@@ -960,7 +976,16 @@ function resumeMountClassInstance(
976 // If an update was already in progress, we should schedule an Update
977 // effect even though we're bailing out, so that cWU/cDU are called.
978 if (typeof instance.componentDidMount === 'function') {
963 - workInProgress.flags |= Update;
979 + if (
980 + __DEV__ &&
981 + enableDoubleInvokingEffects &&
982 + (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
983 + ) {
984 + // Never double-invoke effects for legacy roots.
985 + workInProgress.flags |= MountLayoutDev | Update;
986 + } else {
987 + workInProgress.flags |= Update;
988 + }
989 }
990 return false;
991 }
@@ -1003,13 +1028,31 @@ function resumeMountClassInstance(
1028 }
1029 }
1030 if (typeof instance.componentDidMount === 'function') {
1006 - workInProgress.flags |= Update;
1031 + if (
1032 + __DEV__ &&
1033 + enableDoubleInvokingEffects &&
1034 + (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1035 + ) {
1036 + // Never double-invoke effects for legacy roots.
1037 + workInProgress.flags |= MountLayoutDev | Update;
1038 + } else {
1039 + workInProgress.flags |= Update;
1040 + }
1041 }
1042 } else {
1043 // If an update was already in progress, we should schedule an Update
1044 // effect even though we're bailing out, so that cWU/cDU are called.
1045 if (typeof instance.componentDidMount === 'function') {
1012 - workInProgress.flags |= Update;
1046 + if (
1047 + __DEV__ &&
1048 + enableDoubleInvokingEffects &&
1049 + (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1050 + ) {
1051 + // Never double-invoke effects for legacy roots.
1052 + workInProgress.flags |= MountLayoutDev | Update;
1053 + } else {
1054 + workInProgress.flags |= Update;
1055 + }
1056 }
1057
1058 // If shouldComponentUpdate returned false, we should still update the
packages/react-reconciler/src/ReactFiberClassComponent.old.js
+49 -6
@@ -12,13 +12,14 @@ import type {Lanes} from './ReactFiberLane.old';
12 import type {UpdateQueue} from './ReactUpdateQueue.old';
13
14 import * as React from 'react';
15 -import {Update, Snapshot} from './ReactFiberFlags';
15 +import {MountLayoutDev, Update, Snapshot} from './ReactFiberFlags';
16 import {
17 debugRenderPhaseSideEffectsForStrictMode,
18 disableLegacyContext,
19 enableDebugTracing,
20 enableSchedulingProfiler,
21 warnAboutDeprecatedLifecycles,
22 + enableDoubleInvokingEffects,
23 } from 'shared/ReactFeatureFlags';
24 import ReactStrictModeWarnings from './ReactStrictModeWarnings.old';
25 import {isMounted} from './ReactFiberTreeReflection';
@@ -29,7 +30,13 @@ import invariant from 'shared/invariant';
30 import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
31
32 import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
32 -import {DebugTracingMode, StrictMode} from './ReactTypeOfMode';
33 +import {
34 + BlockingMode,
35 + ConcurrentMode,
36 + DebugTracingMode,
37 + NoMode,
38 + StrictMode,
39 +} from './ReactTypeOfMode';
40
41 import {
42 enqueueUpdate,
@@ -890,7 +897,16 @@ function mountClassInstance(
897 }
898
899 if (typeof instance.componentDidMount === 'function') {
893 - workInProgress.flags |= Update;
900 + if (
901 + __DEV__ &&
902 + enableDoubleInvokingEffects &&
903 + (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
904 + ) {
905 + // Never double-invoke effects for legacy roots.
906 + workInProgress.flags |= MountLayoutDev | Update;
907 + } else {
908 + workInProgress.flags |= Update;
909 + }
910 }
911 }
912
@@ -960,7 +976,16 @@ function resumeMountClassInstance(
976 // If an update was already in progress, we should schedule an Update
977 // effect even though we're bailing out, so that cWU/cDU are called.
978 if (typeof instance.componentDidMount === 'function') {
963 - workInProgress.flags |= Update;
979 + if (
980 + __DEV__ &&
981 + enableDoubleInvokingEffects &&
982 + (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
983 + ) {
984 + // Never double-invoke effects for legacy roots.
985 + workInProgress.flags |= MountLayoutDev | Update;
986 + } else {
987 + workInProgress.flags |= Update;
988 + }
989 }
990 return false;
991 }
@@ -1003,13 +1028,31 @@ function resumeMountClassInstance(
1028 }
1029 }
1030 if (typeof instance.componentDidMount === 'function') {
1006 - workInProgress.flags |= Update;
1031 + if (
1032 + __DEV__ &&
1033 + enableDoubleInvokingEffects &&
1034 + (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1035 + ) {
1036 + // Never double-invoke effects for legacy roots.
1037 + workInProgress.flags |= MountLayoutDev | Update;
1038 + } else {
1039 + workInProgress.flags |= Update;
1040 + }
1041 }
1042 } else {
1043 // If an update was already in progress, we should schedule an Update
1044 // effect even though we're bailing out, so that cWU/cDU are called.
1045 if (typeof instance.componentDidMount === 'function') {
1012 - workInProgress.flags |= Update;
1046 + if (
1047 + __DEV__ &&
1048 + enableDoubleInvokingEffects &&
1049 + (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1050 + ) {
1051 + // Never double-invoke effects for legacy roots.
1052 + workInProgress.flags |= MountLayoutDev | Update;
1053 + } else {
1054 + workInProgress.flags |= Update;
1055 + }
1056 }
1057
1058 // If shouldComponentUpdate returned false, we should still update the
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+134
@@ -36,6 +36,7 @@ import {
36 enableFundamentalAPI,
37 enableSuspenseCallback,
38 enableScopeAPI,
39 + enableDoubleInvokingEffects,
40 } from 'shared/ReactFeatureFlags';
41 import {
42 FunctionComponent,
@@ -2436,6 +2437,135 @@ function ensureCorrectReturnPointer(fiber, expectedReturnFiber) {
2437 fiber.return = expectedReturnFiber;
2438 }
2439
2440 +function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
2441 + if (__DEV__ && enableDoubleInvokingEffects) {
2442 + // We don't need to re-check for legacy roots here.
2443 + // This function will not be called within legacy roots.
2444 + switch (fiber.tag) {
2445 + case FunctionComponent:
2446 + case ForwardRef:
2447 + case SimpleMemoComponent: {
2448 + invokeGuardedCallback(
2449 + null,
2450 + commitHookEffectListMount,
2451 + null,
2452 + HookLayout | HookHasEffect,
2453 + fiber,
2454 + );
2455 + if (hasCaughtError()) {
2456 + const mountError = clearCaughtError();
2457 + captureCommitPhaseError(fiber, mountError);
2458 + }
2459 + break;
2460 + }
2461 + case ClassComponent: {
2462 + const instance = fiber.stateNode;
2463 + invokeGuardedCallback(null, instance.componentDidMount, instance);
2464 + if (hasCaughtError()) {
2465 + const mountError = clearCaughtError();
2466 + captureCommitPhaseError(fiber, mountError);
2467 + }
2468 + break;
2469 + }
2470 + }
2471 + }
2472 +}
2473 +
2474 +function invokePassiveEffectMountInDEV(fiber: Fiber): void {
2475 + if (__DEV__ && enableDoubleInvokingEffects) {
2476 + // We don't need to re-check for legacy roots here.
2477 + // This function will not be called within legacy roots.
2478 + switch (fiber.tag) {
2479 + case FunctionComponent:
2480 + case ForwardRef:
2481 + case SimpleMemoComponent: {
2482 + invokeGuardedCallback(
2483 + null,
2484 + commitHookEffectListMount,
2485 + null,
2486 + HookPassive | HookHasEffect,
2487 + fiber,
2488 + );
2489 + if (hasCaughtError()) {
2490 + const mountError = clearCaughtError();
2491 + captureCommitPhaseError(fiber, mountError);
2492 + }
2493 + break;
2494 + }
2495 + }
2496 + }
2497 +}
2498 +
2499 +function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
2500 + if (__DEV__ && enableDoubleInvokingEffects) {
2501 + // We don't need to re-check for legacy roots here.
2502 + // This function will not be called within legacy roots.
2503 + switch (fiber.tag) {
2504 + case FunctionComponent:
2505 + case ForwardRef:
2506 + case SimpleMemoComponent: {
2507 + invokeGuardedCallback(
2508 + null,
2509 + commitHookEffectListUnmount,
2510 + null,
2511 + HookLayout | HookHasEffect,
2512 + fiber,
2513 + fiber.return,
2514 + );
2515 + if (hasCaughtError()) {
2516 + const unmountError = clearCaughtError();
2517 + captureCommitPhaseError(fiber, unmountError);
2518 + }
2519 + break;
2520 + }
2521 + case ClassComponent: {
2522 + const instance = fiber.stateNode;
2523 + if (typeof instance.componentWillUnmount === 'function') {
2524 + invokeGuardedCallback(
2525 + null,
2526 + safelyCallComponentWillUnmount,
2527 + null,
2528 + fiber,
2529 + instance,
2530 + fiber.return,
2531 + );
2532 + if (hasCaughtError()) {
2533 + const unmountError = clearCaughtError();
2534 + captureCommitPhaseError(fiber, unmountError);
2535 + }
2536 + }
2537 + break;
2538 + }
2539 + }
2540 + }
2541 +}
2542 +
2543 +function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
2544 + if (__DEV__ && enableDoubleInvokingEffects) {
2545 + // We don't need to re-check for legacy roots here.
2546 + // This function will not be called within legacy roots.
2547 + switch (fiber.tag) {
2548 + case FunctionComponent:
2549 + case ForwardRef:
2550 + case SimpleMemoComponent: {
2551 + invokeGuardedCallback(
2552 + null,
2553 + commitHookEffectListUnmount,
2554 + null,
2555 + HookPassive | HookHasEffect,
2556 + fiber,
2557 + fiber.return,
2558 + );
2559 + if (hasCaughtError()) {
2560 + const unmountError = clearCaughtError();
2561 + captureCommitPhaseError(fiber, unmountError);
2562 + }
2563 + break;
2564 + }
2565 + }
2566 + }
2567 +}
2568 +
2569 export {
2570 commitResetTextContent,
2571 commitPlacement,
@@ -2443,4 +2573,8 @@ export {
2573 commitWork,
2574 commitAttachRef,
2575 commitDetachRef,
2576 + invokeLayoutEffectMountInDEV,
2577 + invokeLayoutEffectUnmountInDEV,
2578 + invokePassiveEffectMountInDEV,
2579 + invokePassiveEffectUnmountInDEV,
2580 };
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+134
@@ -36,6 +36,7 @@ import {
36 enableFundamentalAPI,
37 enableSuspenseCallback,
38 enableScopeAPI,
39 + enableDoubleInvokingEffects,
40 } from 'shared/ReactFeatureFlags';
41 import {
42 FunctionComponent,
@@ -2352,6 +2353,135 @@ function ensureCorrectReturnPointer(fiber, expectedReturnFiber) {
2353 fiber.return = expectedReturnFiber;
2354 }
2355
2356 +function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
2357 + if (__DEV__ && enableDoubleInvokingEffects) {
2358 + // We don't need to re-check for legacy roots here.
2359 + // This function will not be called within legacy roots.
2360 + switch (fiber.tag) {
2361 + case FunctionComponent:
2362 + case ForwardRef:
2363 + case SimpleMemoComponent: {
2364 + invokeGuardedCallback(
2365 + null,
2366 + commitHookEffectListMount,
2367 + null,
2368 + HookLayout | HookHasEffect,
2369 + fiber,
2370 + );
2371 + if (hasCaughtError()) {
2372 + const mountError = clearCaughtError();
2373 + captureCommitPhaseError(fiber, mountError);
2374 + }
2375 + break;
2376 + }
2377 + case ClassComponent: {
2378 + const instance = fiber.stateNode;
2379 + invokeGuardedCallback(null, instance.componentDidMount, instance);
2380 + if (hasCaughtError()) {
2381 + const mountError = clearCaughtError();
2382 + captureCommitPhaseError(fiber, mountError);
2383 + }
2384 + break;
2385 + }
2386 + }
2387 + }
2388 +}
2389 +
2390 +function invokePassiveEffectMountInDEV(fiber: Fiber): void {
2391 + if (__DEV__ && enableDoubleInvokingEffects) {
2392 + // We don't need to re-check for legacy roots here.
2393 + // This function will not be called within legacy roots.
2394 + switch (fiber.tag) {
2395 + case FunctionComponent:
2396 + case ForwardRef:
2397 + case SimpleMemoComponent: {
2398 + invokeGuardedCallback(
2399 + null,
2400 + commitHookEffectListMount,
2401 + null,
2402 + HookPassive | HookHasEffect,
2403 + fiber,
2404 + );
2405 + if (hasCaughtError()) {
2406 + const mountError = clearCaughtError();
2407 + captureCommitPhaseError(fiber, mountError);
2408 + }
2409 + break;
2410 + }
2411 + }
2412 + }
2413 +}
2414 +
2415 +function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
2416 + if (__DEV__ && enableDoubleInvokingEffects) {
2417 + // We don't need to re-check for legacy roots here.
2418 + // This function will not be called within legacy roots.
2419 + switch (fiber.tag) {
2420 + case FunctionComponent:
2421 + case ForwardRef:
2422 + case SimpleMemoComponent: {
2423 + invokeGuardedCallback(
2424 + null,
2425 + commitHookEffectListUnmount,
2426 + null,
2427 + HookLayout | HookHasEffect,
2428 + fiber,
2429 + fiber.return,
2430 + );
2431 + if (hasCaughtError()) {
2432 + const unmountError = clearCaughtError();
2433 + captureCommitPhaseError(fiber, unmountError);
2434 + }
2435 + break;
2436 + }
2437 + case ClassComponent: {
2438 + const instance = fiber.stateNode;
2439 + if (typeof instance.componentWillUnmount === 'function') {
2440 + invokeGuardedCallback(
2441 + null,
2442 + safelyCallComponentWillUnmount,
2443 + null,
2444 + fiber,
2445 + instance,
2446 + fiber.return,
2447 + );
2448 + if (hasCaughtError()) {
2449 + const unmountError = clearCaughtError();
2450 + captureCommitPhaseError(fiber, unmountError);
2451 + }
2452 + }
2453 + break;
2454 + }
2455 + }
2456 + }
2457 +}
2458 +
2459 +function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
2460 + if (__DEV__ && enableDoubleInvokingEffects) {
2461 + // We don't need to re-check for legacy roots here.
2462 + // This function will not be called within legacy roots.
2463 + switch (fiber.tag) {
2464 + case FunctionComponent:
2465 + case ForwardRef:
2466 + case SimpleMemoComponent: {
2467 + invokeGuardedCallback(
2468 + null,
2469 + commitHookEffectListUnmount,
2470 + null,
2471 + HookPassive | HookHasEffect,
2472 + fiber,
2473 + fiber.return,
2474 + );
2475 + if (hasCaughtError()) {
2476 + const unmountError = clearCaughtError();
2477 + captureCommitPhaseError(fiber, unmountError);
2478 + }
2479 + break;
2480 + }
2481 + }
2482 + }
2483 +}
2484 +
2485 export {
2486 commitBeforeMutationLifeCycles,
2487 commitResetTextContent,
@@ -2360,4 +2490,8 @@ export {
2490 commitWork,
2491 commitAttachRef,
2492 commitDetachRef,
2493 + invokeLayoutEffectMountInDEV,
2494 + invokeLayoutEffectUnmountInDEV,
2495 + invokePassiveEffectMountInDEV,
2496 + invokePassiveEffectUnmountInDEV,
2497 };
packages/react-reconciler/src/ReactFiberHooks.new.js
+80 -16
@@ -29,9 +29,15 @@ import {
29 enableCache,
30 decoupleUpdatePriorityFromScheduler,
31 enableUseRefAccessWarning,
32 + enableDoubleInvokingEffects,
33 } from 'shared/ReactFeatureFlags';
34
34 -import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
35 +import {
36 + NoMode,
37 + BlockingMode,
38 + ConcurrentMode,
39 + DebugTracingMode,
40 +} from './ReactTypeOfMode';
41 import {
42 NoLane,
43 NoLanes,
@@ -52,6 +58,8 @@ import {
58 Update as UpdateEffect,
59 Passive as PassiveEffect,
60 PassiveStatic as PassiveStaticEffect,
61 + MountLayoutDev as MountLayoutDevEffect,
62 + MountPassiveDev as MountPassiveDevEffect,
63 } from './ReactFiberFlags';
64 import {
65 HasEffect as HookHasEffect,
@@ -474,7 +482,20 @@ export function bailoutHooks(
482 workInProgress.updateQueue = current.updateQueue;
483 // TODO: Don't need to reset the flags here, because they're reset in the
484 // complete phase (bubbleProperties).
477 - workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
485 + if (
486 + __DEV__ &&
487 + enableDoubleInvokingEffects &&
488 + (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
489 + ) {
490 + workInProgress.flags &= ~(
491 + MountPassiveDevEffect |
492 + MountLayoutDevEffect |
493 + PassiveEffect |
494 + UpdateEffect
495 + );
496 + } else {
497 + workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
498 + }
499 current.lanes = removeLanes(current.lanes, lanes);
500 }
501
@@ -1310,12 +1331,25 @@ function mountEffect(
1331 warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
1332 }
1333 }
1313 - return mountEffectImpl(
1314 - PassiveEffect | PassiveStaticEffect,
1315 - HookPassive,
1316 - create,
1317 - deps,
1318 - );
1334 + if (
1335 + __DEV__ &&
1336 + enableDoubleInvokingEffects &&
1337 + (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1338 + ) {
1339 + return mountEffectImpl(
1340 + MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
1341 + HookPassive,
1342 + create,
1343 + deps,
1344 + );
1345 + } else {
1346 + return mountEffectImpl(
1347 + PassiveEffect | PassiveStaticEffect,
1348 + HookPassive,
1349 + create,
1350 + deps,
1351 + );
1352 + }
1353 }
1354
1355 function updateEffect(
@@ -1335,7 +1369,20 @@ function mountLayoutEffect(
1369 create: () => (() => void) | void,
1370 deps: Array<mixed> | void | null,
1371 ): void {
1338 - return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
1372 + if (
1373 + __DEV__ &&
1374 + enableDoubleInvokingEffects &&
1375 + (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1376 + ) {
1377 + return mountEffectImpl(
1378 + MountLayoutDevEffect | UpdateEffect,
1379 + HookLayout,
1380 + create,
1381 + deps,
1382 + );
1383 + } else {
1384 + return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
1385 + }
1386 }
1387
1388 function updateLayoutEffect(
@@ -1394,12 +1441,25 @@ function mountImperativeHandle<T>(
1441 const effectDeps =
1442 deps !== null && deps !== undefined ? deps.concat([ref]) : null;
1443
1397 - return mountEffectImpl(
1398 - UpdateEffect,
1399 - HookLayout,
1400 - imperativeHandleEffect.bind(null, create, ref),
1401 - effectDeps,
1402 - );
1444 + if (
1445 + __DEV__ &&
1446 + enableDoubleInvokingEffects &&
1447 + (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1448 + ) {
1449 + return mountEffectImpl(
1450 + MountLayoutDevEffect | UpdateEffect,
1451 + HookLayout,
1452 + imperativeHandleEffect.bind(null, create, ref),
1453 + effectDeps,
1454 + );
1455 + } else {
1456 + return mountEffectImpl(
1457 + UpdateEffect,
1458 + HookLayout,
1459 + imperativeHandleEffect.bind(null, create, ref),
1460 + effectDeps,
1461 + );
1462 + }
1463 }
1464
1465 function updateImperativeHandle<T>(
@@ -1680,7 +1740,11 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
1740 const setId = mountState(id)[1];
1741
1742 if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
1683 - currentlyRenderingFiber.flags |= PassiveEffect;
1743 + if (__DEV__ && enableDoubleInvokingEffects) {
1744 + currentlyRenderingFiber.flags |= MountPassiveDevEffect | PassiveEffect;
1745 + } else {
1746 + currentlyRenderingFiber.flags |= PassiveEffect;
1747 + }
1748 pushEffect(
1749 HookHasEffect | HookPassive,
1750 () => {
packages/react-reconciler/src/ReactFiberHooks.old.js
+86 -16
@@ -29,9 +29,15 @@ import {
29 enableCache,
30 decoupleUpdatePriorityFromScheduler,
31 enableUseRefAccessWarning,
32 + enableDoubleInvokingEffects,
33 } from 'shared/ReactFeatureFlags';
34
34 -import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
35 +import {
36 + NoMode,
37 + BlockingMode,
38 + ConcurrentMode,
39 + DebugTracingMode,
40 +} from './ReactTypeOfMode';
41 import {
42 NoLane,
43 NoLanes,
@@ -52,6 +58,8 @@ import {
58 Update as UpdateEffect,
59 Passive as PassiveEffect,
60 PassiveStatic as PassiveStaticEffect,
61 + MountLayoutDev as MountLayoutDevEffect,
62 + MountPassiveDev as MountPassiveDevEffect,
63 } from './ReactFiberFlags';
64 import {
65 HasEffect as HookHasEffect,
@@ -472,7 +480,22 @@ export function bailoutHooks(
480 lanes: Lanes,
481 ) {
482 workInProgress.updateQueue = current.updateQueue;
475 - workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
483 + // TODO: Don't need to reset the flags here, because they're reset in the
484 + // complete phase (bubbleProperties).
485 + if (
486 + __DEV__ &&
487 + enableDoubleInvokingEffects &&
488 + (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
489 + ) {
490 + workInProgress.flags &= ~(
491 + MountLayoutDevEffect |
492 + MountPassiveDevEffect |
493 + PassiveEffect |
494 + UpdateEffect
495 + );
496 + } else {
497 + workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
498 + }
499 current.lanes = removeLanes(current.lanes, lanes);
500 }
501
@@ -1308,12 +1331,28 @@ function mountEffect(
1331 warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
1332 }
1333 }
1311 - return mountEffectImpl(
1312 - UpdateEffect | PassiveEffect | PassiveStaticEffect,
1313 - HookPassive,
1314 - create,
1315 - deps,
1316 - );
1334 + if (
1335 + __DEV__ &&
1336 + enableDoubleInvokingEffects &&
1337 + (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1338 + ) {
1339 + return mountEffectImpl(
1340 + MountPassiveDevEffect |
1341 + UpdateEffect |
1342 + PassiveEffect |
1343 + PassiveStaticEffect,
1344 + HookPassive,
1345 + create,
1346 + deps,
1347 + );
1348 + } else {
1349 + return mountEffectImpl(
1350 + UpdateEffect | PassiveEffect | PassiveStaticEffect,
1351 + HookPassive,
1352 + create,
1353 + deps,
1354 + );
1355 + }
1356 }
1357
1358 function updateEffect(
@@ -1338,7 +1377,20 @@ function mountLayoutEffect(
1377 create: () => (() => void) | void,
1378 deps: Array<mixed> | void | null,
1379 ): void {
1341 - return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
1380 + if (
1381 + __DEV__ &&
1382 + enableDoubleInvokingEffects &&
1383 + (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1384 + ) {
1385 + return mountEffectImpl(
1386 + MountLayoutDevEffect | UpdateEffect,
1387 + HookLayout,
1388 + create,
1389 + deps,
1390 + );
1391 + } else {
1392 + return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
1393 + }
1394 }
1395
1396 function updateLayoutEffect(
@@ -1397,12 +1449,25 @@ function mountImperativeHandle<T>(
1449 const effectDeps =
1450 deps !== null && deps !== undefined ? deps.concat([ref]) : null;
1451
1400 - return mountEffectImpl(
1401 - UpdateEffect,
1402 - HookLayout,
1403 - imperativeHandleEffect.bind(null, create, ref),
1404 - effectDeps,
1405 - );
1452 + if (
1453 + __DEV__ &&
1454 + enableDoubleInvokingEffects &&
1455 + (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1456 + ) {
1457 + return mountEffectImpl(
1458 + MountLayoutDevEffect | UpdateEffect,
1459 + HookLayout,
1460 + imperativeHandleEffect.bind(null, create, ref),
1461 + effectDeps,
1462 + );
1463 + } else {
1464 + return mountEffectImpl(
1465 + UpdateEffect,
1466 + HookLayout,
1467 + imperativeHandleEffect.bind(null, create, ref),
1468 + effectDeps,
1469 + );
1470 + }
1471 }
1472
1473 function updateImperativeHandle<T>(
@@ -1683,7 +1748,12 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
1748 const setId = mountState(id)[1];
1749
1750 if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
1686 - currentlyRenderingFiber.flags |= UpdateEffect | PassiveEffect;
1751 + if (__DEV__ && enableDoubleInvokingEffects) {
1752 + currentlyRenderingFiber.flags |=
1753 + MountPassiveDevEffect | UpdateEffect | PassiveEffect;
1754 + } else {
1755 + currentlyRenderingFiber.flags |= UpdateEffect | PassiveEffect;
1756 + }
1757 pushEffect(
1758 HookHasEffect | HookPassive,
1759 () => {
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+72
@@ -15,6 +15,7 @@ import type {Interaction} from 'scheduler/src/Tracing';
15 import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
16 import type {StackCursor} from './ReactFiberStack.new';
17 import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
18 +import type {Flags} from './ReactFiberFlags';
19
20 import {
21 warnAboutDeprecatedLifecycles,
@@ -31,6 +32,7 @@ import {
32 enableDebugTracing,
33 enableSchedulingProfiler,
34 disableSchedulerTimeoutInWorkLoop,
35 + enableDoubleInvokingEffects,
36 } from 'shared/ReactFeatureFlags';
37 import ReactSharedInternals from 'shared/ReactSharedInternals';
38 import invariant from 'shared/invariant';
@@ -126,6 +128,8 @@ import {
128 MutationMask,
129 LayoutMask,
130 PassiveMask,
131 + MountPassiveDev,
132 + MountLayoutDev,
133 } from './ReactFiberFlags';
134 import {
135 NoLanePriority,
@@ -182,6 +186,10 @@ import {
186 commitPassiveEffectDurations,
187 commitPassiveMountEffects,
188 commitPassiveUnmountEffects,
189 + invokeLayoutEffectMountInDEV,
190 + invokePassiveEffectMountInDEV,
191 + invokeLayoutEffectUnmountInDEV,
192 + invokePassiveEffectUnmountInDEV,
193 } from './ReactFiberCommitWork.new';
194 import {enqueueUpdate} from './ReactUpdateQueue.new';
195 import {resetContextDependencies} from './ReactFiberNewContext.new';
@@ -2041,6 +2049,12 @@ function commitRootImpl(root, renderPriorityLevel) {
2049 legacyErrorBoundariesThatAlreadyFailed = null;
2050 }
2051
2052 + if (__DEV__ && enableDoubleInvokingEffects) {
2053 + if (!rootDidHavePassiveEffects) {
2054 + commitDoubleInvokeEffectsInDEV(root.current, false);
2055 + }
2056 + }
2057 +
2058 if (enableSchedulerTracing) {
2059 if (!rootDidHavePassiveEffects) {
2060 // If there are no passive effects, then we can complete the pending interactions.
@@ -2222,6 +2236,10 @@ function flushPassiveEffectsImpl() {
2236 markPassiveEffectsStopped();
2237 }
2238
2239 + if (__DEV__ && enableDoubleInvokingEffects) {
2240 + commitDoubleInvokeEffectsInDEV(root.current, true);
2241 + }
2242 +
2243 executionContext = prevExecutionContext;
2244
2245 flushSyncCallbackQueue();
@@ -2505,6 +2523,60 @@ function flushRenderPhaseStrictModeWarningsInDEV() {
2523 }
2524 }
2525
2526 +function commitDoubleInvokeEffectsInDEV(
2527 + fiber: Fiber,
2528 + hasPassiveEffects: boolean,
2529 +) {
2530 + if (__DEV__ && enableDoubleInvokingEffects) {
2531 + // Never double-invoke effects for legacy roots.
2532 + if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) {
2533 + return;
2534 + }
2535 +
2536 + setCurrentDebugFiberInDEV(fiber);
2537 + invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
2538 + if (hasPassiveEffects) {
2539 + invokeEffectsInDev(
2540 + fiber,
2541 + MountPassiveDev,
2542 + invokePassiveEffectUnmountInDEV,
2543 + );
2544 + }
2545 +
2546 + invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
2547 + if (hasPassiveEffects) {
2548 + invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
2549 + }
2550 + resetCurrentDebugFiberInDEV();
2551 + }
2552 +}
2553 +
2554 +// TODO (strict effects) Rewrite to be iterative
2555 +function invokeEffectsInDev(
2556 + firstChild: Fiber,
2557 + fiberFlags: Flags,
2558 + invokeEffectFn: (fiber: Fiber) => void,
2559 +): void {
2560 + if (__DEV__ && enableDoubleInvokingEffects) {
2561 + // We don't need to re-check for legacy roots here.
2562 + // This function will not be called within legacy roots.
2563 + let fiber = firstChild;
2564 + while (fiber !== null) {
2565 + if (fiber.child !== null) {
2566 + const primarySubtreeFlag = fiber.subtreeFlags & fiberFlags;
2567 + if (primarySubtreeFlag !== NoFlags) {
2568 + invokeEffectsInDev(fiber.child, fiberFlags, invokeEffectFn);
2569 + }
2570 + }
2571 +
2572 + if ((fiber.flags & fiberFlags) !== NoFlags) {
2573 + invokeEffectFn(fiber);
2574 + }
2575 + fiber = fiber.sibling;
2576 + }
2577 + }
2578 +}
2579 +
2580 let didWarnStateUpdateForNotYetMountedComponent: Set<string> | null = null;
2581 function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
2582 if (__DEV__) {
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+70
@@ -16,6 +16,7 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
16 import type {Effect as HookEffect} from './ReactFiberHooks.old';
17 import type {StackCursor} from './ReactFiberStack.old';
18 import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old';
19 +import type {Flags} from './ReactFiberFlags';
20
21 import {
22 warnAboutDeprecatedLifecycles,
@@ -32,6 +33,7 @@ import {
33 enableDebugTracing,
34 enableSchedulingProfiler,
35 disableSchedulerTimeoutInWorkLoop,
36 + enableDoubleInvokingEffects,
37 } from 'shared/ReactFeatureFlags';
38 import ReactSharedInternals from 'shared/ReactSharedInternals';
39 import invariant from 'shared/invariant';
@@ -131,6 +133,8 @@ import {
133 HostEffectMask,
134 Hydrating,
135 StaticMask,
136 + MountPassiveDev,
137 + MountLayoutDev,
138 } from './ReactFiberFlags';
139 import {
140 NoLanePriority,
@@ -189,6 +193,10 @@ import {
193 commitPassiveMountEffects,
194 commitPassiveUnmountEffects,
195 detachFiberAfterEffects,
196 + invokeLayoutEffectMountInDEV,
197 + invokePassiveEffectMountInDEV,
198 + invokeLayoutEffectUnmountInDEV,
199 + invokePassiveEffectUnmountInDEV,
200 } from './ReactFiberCommitWork.old';
201 import {enqueueUpdate} from './ReactUpdateQueue.old';
202 import {resetContextDependencies} from './ReactFiberNewContext.old';
@@ -2126,6 +2134,12 @@ function commitRootImpl(root, renderPriorityLevel) {
2134 legacyErrorBoundariesThatAlreadyFailed = null;
2135 }
2136
2137 + if (__DEV__ && enableDoubleInvokingEffects) {
2138 + if (!rootDidHavePassiveEffects) {
2139 + commitDoubleInvokeEffectsInDEV(root.current, false);
2140 + }
2141 + }
2142 +
2143 if (enableSchedulerTracing) {
2144 if (!rootDidHavePassiveEffects) {
2145 // If there are no passive effects, then we can complete the pending interactions.
@@ -2379,6 +2393,10 @@ function flushPassiveEffectsImpl() {
2393 markPassiveEffectsStopped();
2394 }
2395
2396 + if (__DEV__ && enableDoubleInvokingEffects) {
2397 + commitDoubleInvokeEffectsInDEV(root.current, true);
2398 + }
2399 +
2400 executionContext = prevExecutionContext;
2401
2402 flushSyncCallbackQueue();
@@ -2662,6 +2680,58 @@ function flushRenderPhaseStrictModeWarningsInDEV() {
2680 }
2681 }
2682
2683 +function commitDoubleInvokeEffectsInDEV(
2684 + fiber: Fiber,
2685 + hasPassiveEffects: boolean,
2686 +) {
2687 + if (__DEV__ && enableDoubleInvokingEffects) {
2688 + // Never double-invoke effects for legacy roots.
2689 + if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) {
2690 + return;
2691 + }
2692 +
2693 + setCurrentDebugFiberInDEV(fiber);
2694 + invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
2695 + if (hasPassiveEffects) {
2696 + invokeEffectsInDev(
2697 + fiber,
2698 + MountPassiveDev,
2699 + invokePassiveEffectUnmountInDEV,
2700 + );
2701 + }
2702 +
2703 + invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
2704 + if (hasPassiveEffects) {
2705 + invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
2706 + }
2707 + resetCurrentDebugFiberInDEV();
2708 + }
2709 +}
2710 +
2711 +// TODO (strict effects) Rewrite to be iterative
2712 +function invokeEffectsInDev(
2713 + firstChild: Fiber,
2714 + fiberFlags: Flags,
2715 + invokeEffectFn: (fiber: Fiber) => void,
2716 +): void {
2717 + if (__DEV__ && enableDoubleInvokingEffects) {
2718 + let fiber = firstChild;
2719 + while (fiber !== null) {
2720 + if (fiber.child !== null) {
2721 + const primarySubtreeFlag = fiber.subtreeFlags & fiberFlags;
2722 + if (primarySubtreeFlag !== NoFlags) {
2723 + invokeEffectsInDev(fiber.child, fiberFlags, invokeEffectFn);
2724 + }
2725 + }
2726 +
2727 + if ((fiber.flags & fiberFlags) !== NoFlags) {
2728 + invokeEffectFn(fiber);
2729 + }
2730 + fiber = fiber.sibling;
2731 + }
2732 + }
2733 +}
2734 +
2735 let didWarnStateUpdateForNotYetMountedComponent: Set<string> | null = null;
2736 function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
2737 if (__DEV__) {
packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.internal.js
+33 -56
@@ -15,8 +15,9 @@ let ReactNoop;
15 let Scheduler;
16
17 function shouldDoubleInvokingEffects() {
18 - // Reverted temporarily. Need to re-land.
19 - return false;
18 + // For now, this feature only exists in the old fork (while the new fork is being bisected).
19 + // Eventually we'll land it in both forks.
20 + return __DEV__;
21 }
22
23 describe('ReactDoubleInvokeEvents', () => {
@@ -30,7 +31,7 @@ describe('ReactDoubleInvokeEvents', () => {
31 ReactFeatureFlags.enableDoubleInvokingEffects = shouldDoubleInvokingEffects();
32 });
33
33 - it('should not double invoke effects outside of StrictMode', () => {
34 + it('should not double invoke effects in legacy mode', () => {
35 function App({text}) {
36 React.useEffect(() => {
37 Scheduler.unstable_yieldValue('useEffect mount');
@@ -55,54 +56,30 @@ describe('ReactDoubleInvokeEvents', () => {
56 ]);
57 });
58
58 - it('should double invoke effects only within StrictMode subtrees', () => {
59 - function ComponentWithEffects({label}) {
60 - React.useEffect(() => {
61 - Scheduler.unstable_yieldValue(`useEffect mount "${label}"`);
62 - return () =>
63 - Scheduler.unstable_yieldValue(`useEffect unmount "${label}"`);
64 - });
59 + it('should not double invoke class lifecycles in legacy mode', () => {
60 + class App extends React.PureComponent {
61 + componentDidMount() {
62 + Scheduler.unstable_yieldValue('componentDidMount');
63 + }
64
66 - React.useLayoutEffect(() => {
67 - Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`);
68 - return () =>
69 - Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`);
70 - });
65 + componentDidUpdate() {
66 + Scheduler.unstable_yieldValue('componentDidUpdate');
67 + }
68
72 - return label;
69 + componentWillUnmount() {
70 + Scheduler.unstable_yieldValue('componentWillUnmount');
71 + }
72 +
73 + render() {
74 + return this.props.text;
75 + }
76 }
77
78 ReactNoop.act(() => {
76 - ReactNoop.renderLegacySyncRoot(
77 - <>
78 - <ComponentWithEffects label={'loose'} />
79 - <React.StrictMode>
80 - <ComponentWithEffects label={'strict'} />
81 - </React.StrictMode>
82 - </>,
83 - );
79 + ReactNoop.renderLegacySyncRoot(<App text={'mount'} />);
80 });
81
86 - if (shouldDoubleInvokingEffects()) {
87 - expect(Scheduler).toHaveYielded([
88 - 'useLayoutEffect mount "loose"',
89 - 'useLayoutEffect mount "strict"',
90 - 'useEffect mount "loose"',
91 - 'useEffect mount "strict"',
92 -
93 - 'useLayoutEffect unmount "strict"',
94 - 'useEffect unmount "strict"',
95 - 'useLayoutEffect mount "strict"',
96 - 'useEffect mount "strict"',
97 - ]);
98 - } else {
99 - expect(Scheduler).toHaveYielded([
100 - 'useLayoutEffect mount "loose"',
101 - 'useLayoutEffect mount "strict"',
102 - 'useEffect mount "loose"',
103 - 'useEffect mount "strict"',
104 - ]);
105 - }
82 + expect(Scheduler).toHaveYielded(['componentDidMount']);
83 });
84
85 it('should flush double-invoked effects within the same frame as layout effects if there are no passive effects', () => {
@@ -117,10 +94,10 @@ describe('ReactDoubleInvokeEvents', () => {
94 }
95
96 ReactNoop.act(() => {
120 - ReactNoop.renderLegacySyncRoot(
121 - <React.StrictMode>
97 + ReactNoop.render(
98 + <>
99 <ComponentWithEffects label={'one'} />
123 - </React.StrictMode>,
100 + </>,
101 );
102
103 if (shouldDoubleInvokingEffects()) {
@@ -137,11 +114,11 @@ describe('ReactDoubleInvokeEvents', () => {
114 });
115
116 ReactNoop.act(() => {
140 - ReactNoop.renderLegacySyncRoot(
141 - <React.StrictMode>
117 + ReactNoop.render(
118 + <>
119 <ComponentWithEffects label={'one'} />
120 <ComponentWithEffects label={'two'} />
144 - </React.StrictMode>,
121 + </>,
122 );
123
124 if (shouldDoubleInvokingEffects()) {
@@ -185,10 +162,10 @@ describe('ReactDoubleInvokeEvents', () => {
162 }
163
164 ReactNoop.act(() => {
188 - ReactNoop.renderLegacySyncRoot(
189 - <React.StrictMode>
165 + ReactNoop.render(
166 + <>
167 <ComponentWithEffects label={'one'} />
191 - </React.StrictMode>,
168 + </>,
169 );
170
171 if (shouldDoubleInvokingEffects()) {
@@ -211,11 +188,11 @@ describe('ReactDoubleInvokeEvents', () => {
188 });
189
190 ReactNoop.act(() => {
214 - ReactNoop.renderLegacySyncRoot(
215 - <React.StrictMode>
191 + ReactNoop.render(
192 + <>
193 <ComponentWithEffects label={'one'} />
194 <ComponentWithEffects label={'two'} />
218 - </React.StrictMode>,
195 + </>,
196 );
197
198 if (shouldDoubleInvokingEffects()) {
packages/react/src/__tests__/ReactProfiler-test.internal.js
-2
@@ -4874,8 +4874,6 @@ describe('Profiler', () => {
4874 });
4875
4876 if (__DEV__) {
4877 - // @gate enableUseJSStackToTrackPassiveDurations
4878 - // @gate enableDoubleInvokingEffects
4877 it('double invoking does not disconnect wrapped async work', () => {
4878 ReactFeatureFlags.enableDoubleInvokingEffects = true;
4879