diff --git a/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
index 644d7297403..9fbec9f5b04 100644
--- a/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
+++ b/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
@@ -273,6 +273,46 @@ describe('ReactFabric', () => {
expect(nativeFabricUIManager.completeRoot).toBeCalled();
});
+ it('should not clone nodes when layout effects are used', async () => {
+ const View = createReactNativeComponentClass('RCTView', () => ({
+ validAttributes: {foo: true},
+ uiViewClassName: 'RCTView',
+ }));
+
+ const ComponentWithEffect = () => {
+ // Same thing happens with `ref` and `useImperativeHandle`
+ React.useLayoutEffect(() => {});
+ return null;
+ };
+
+ await act(() =>
+ ReactFabric.render(
+
+
+ ,
+ 11,
+ ),
+ );
+ expect(nativeFabricUIManager.completeRoot).toBeCalled();
+ jest.clearAllMocks();
+
+ await act(() =>
+ ReactFabric.render(
+
+
+ ,
+ 11,
+ ),
+ );
+ expect(nativeFabricUIManager.cloneNode).not.toBeCalled();
+ expect(nativeFabricUIManager.cloneNodeWithNewChildren).not.toBeCalled();
+ expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toBeCalled();
+ expect(
+ nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
+ ).not.toBeCalled();
+ expect(nativeFabricUIManager.completeRoot).not.toBeCalled();
+ });
+
it('should call dispatchCommand for native refs', async () => {
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js
index 73889d8b818..c383f4acaab 100644
--- a/packages/react-reconciler/src/ReactFiberCommitWork.js
+++ b/packages/react-reconciler/src/ReactFiberCommitWork.js
@@ -97,6 +97,7 @@ import {
Visibility,
ShouldSuspendCommit,
MaySuspendCommit,
+ Effect,
} from './ReactFiberFlags';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import {
@@ -1062,7 +1063,7 @@ function commitLayoutEffectOnFiber(
finishedWork,
committedLanes,
);
- if (flags & Update) {
+ if (flags & Effect) {
commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
}
break;
@@ -2525,7 +2526,7 @@ function recursivelyTraverseMutationEffects(
}
const prevDebugFiber = getCurrentDebugFiberInDEV();
- if (parentFiber.subtreeFlags & MutationMask) {
+ if (parentFiber.subtreeFlags & (MutationMask | Effect)) {
let child = parentFiber.child;
while (child !== null) {
setCurrentDebugFiberInDEV(child);
diff --git a/packages/react-reconciler/src/ReactFiberFlags.js b/packages/react-reconciler/src/ReactFiberFlags.js
index 5febfa3d528..92f40d0ffd4 100644
--- a/packages/react-reconciler/src/ReactFiberFlags.js
+++ b/packages/react-reconciler/src/ReactFiberFlags.js
@@ -19,7 +19,24 @@ export const DidCapture = /* */ 0b0000000000000000000010000000
export const Hydrating = /* */ 0b0000000000000001000000000000;
// You can change the rest (and add more).
-export const Update = /* */ 0b0000000000000000000000000100;
+
+/**
+ * Work needs to be performed on host components, such as updating props.
+ */
+export const HostComponentUpdate = /* */ 0b0000000000000000000000000100;
+
+/**
+ * Indicates presence of effects (e.g. InsertionEffect, LayoutEffect)
+ */
+export const Effect = /* */ 0b10000000000000000000000000000;
+
+/**
+ * One of the two flags.
+ *
+ * @deprecated Should use the more specific flag to allow us to bail out in more cases.
+ */
+export const Update = HostComponentUpdate | Effect;
+
/* Skipped value: 0b0000000000000000000000001000; */
export const ChildDeletion = /* */ 0b0000000000000000000000010000;
@@ -33,6 +50,9 @@ export const Snapshot = /* */ 0b0000000000000000010000000000
export const Passive = /* */ 0b0000000000000000100000000000;
/* Used by Hydrating: 0b0000000000000001000000000000; */
+/**
+ * Set when the visibility state of Activity/LegacyHidden changed.
+ */
export const Visibility = /* */ 0b0000000000000010000000000000;
export const StoreConsistency = /* */ 0b0000000000000100000000000000;
@@ -89,7 +109,7 @@ export const BeforeMutationMask: number =
export const MutationMask =
Placement |
- Update |
+ HostComponentUpdate |
ChildDeletion |
ContentReset |
Ref |
diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js
index d2c456a5f9c..48a3a376f45 100644
--- a/packages/react-reconciler/src/ReactFiberHooks.js
+++ b/packages/react-reconciler/src/ReactFiberHooks.js
@@ -85,10 +85,10 @@ import {
Passive as PassiveEffect,
PassiveStatic as PassiveStaticEffect,
StaticMask as StaticMaskEffect,
- Update as UpdateEffect,
StoreConsistency,
MountLayoutDev as MountLayoutDevEffect,
MountPassiveDev as MountPassiveDevEffect,
+ Effect as EffectEffect,
} from './ReactFiberFlags';
import {
HasEffect as HookHasEffect,
@@ -883,10 +883,10 @@ export function bailoutHooks(
MountPassiveDevEffect |
MountLayoutDevEffect |
PassiveEffect |
- UpdateEffect
+ EffectEffect
);
} else {
- workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
+ workInProgress.flags &= ~(PassiveEffect | EffectEffect);
}
current.lanes = removeLanes(current.lanes, lanes);
}
@@ -2398,7 +2398,7 @@ function updateEffect(
function useEffectEventImpl) => Return>(
payload: EventFunctionPayload,
) {
- currentlyRenderingFiber.flags |= UpdateEffect;
+ currentlyRenderingFiber.flags |= EffectEffect;
let componentUpdateQueue: null | FunctionComponentUpdateQueue =
(currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
@@ -2453,21 +2453,21 @@ function mountInsertionEffect(
create: () => (() => void) | void,
deps: Array | void | null,
): void {
- mountEffectImpl(UpdateEffect, HookInsertion, create, deps);
+ mountEffectImpl(EffectEffect, HookInsertion, create, deps);
}
function updateInsertionEffect(
create: () => (() => void) | void,
deps: Array | void | null,
): void {
- return updateEffectImpl(UpdateEffect, HookInsertion, create, deps);
+ return updateEffectImpl(EffectEffect, HookInsertion, create, deps);
}
function mountLayoutEffect(
create: () => (() => void) | void,
deps: Array | void | null,
): void {
- let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
+ let fiberFlags: Flags = EffectEffect | LayoutStaticEffect;
if (
__DEV__ &&
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
@@ -2481,7 +2481,7 @@ function updateLayoutEffect(
create: () => (() => void) | void,
deps: Array | void | null,
): void {
- return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
+ return updateEffectImpl(EffectEffect, HookLayout, create, deps);
}
function imperativeHandleEffect(
@@ -2533,7 +2533,7 @@ function mountImperativeHandle(
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
- let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
+ let fiberFlags: Flags = EffectEffect | LayoutStaticEffect;
if (
__DEV__ &&
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
@@ -2568,7 +2568,7 @@ function updateImperativeHandle(
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
updateEffectImpl(
- UpdateEffect,
+ EffectEffect,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js
index cb217d04eba..57b5dc1a350 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js
@@ -124,6 +124,7 @@ import {
Visibility,
MountPassiveDev,
MountLayoutDev,
+ Effect,
} from './ReactFiberFlags';
import {
NoLanes,
@@ -2745,11 +2746,19 @@ function commitRootImpl(
// Reconsider whether this is necessary.
const subtreeHasEffects =
(finishedWork.subtreeFlags &
- (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
+ (BeforeMutationMask |
+ MutationMask |
+ Effect |
+ LayoutMask |
+ PassiveMask)) !==
NoFlags;
const rootHasEffect =
(finishedWork.flags &
- (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
+ (BeforeMutationMask |
+ MutationMask |
+ Effect |
+ LayoutMask |
+ PassiveMask)) !==
NoFlags;
if (subtreeHasEffects || rootHasEffect) {