diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index a272da942e665..422ac830b4071 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -1006,7 +1006,9 @@ function updateProfiler(
return workInProgress.child;
}
-function markRef(current: Fiber | null, workInProgress: Fiber) {
+function markRef(current: Fiber | null, workInProgress: Fiber): boolean {
+ // TODO: This is also where we should check the type of the ref and error if
+ // an invalid one is passed, instead of during child reconcilation.
const ref = workInProgress.ref;
if (
(current === null && ref !== null) ||
@@ -1015,6 +1017,20 @@ function markRef(current: Fiber | null, workInProgress: Fiber) {
// Schedule a Ref effect
workInProgress.flags |= Ref;
workInProgress.flags |= RefStatic;
+ return true;
+ }
+ return false;
+}
+
+function markScopeComponentRef(current: Fiber | null, workInProgress: Fiber) {
+ if (enableScopeAPI) {
+ if (markRef(current, workInProgress)) {
+ // TODO: For some reason, Scope components also need an Update flag if
+ // there's a Ref flag. Probably can be cleaned up but since these are
+ // deprecated and Meta-only anyway I'm not going to bother and will leave
+ // this as-is.
+ workInProgress.flags |= Update;
+ }
}
}
@@ -3531,7 +3547,7 @@ function updateScopeComponent(
) {
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
-
+ markScopeComponentRef(current, workInProgress);
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js
index 639cec6cbf0d5..a6952ca800d0e 100644
--- a/packages/react-reconciler/src/ReactFiberCompleteWork.js
+++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js
@@ -75,8 +75,6 @@ import {
} from './ReactWorkTags';
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
import {
- Ref,
- RefStatic,
Placement,
Update,
Visibility,
@@ -186,10 +184,6 @@ function markUpdate(workInProgress: Fiber) {
workInProgress.flags |= Update;
}
-function markRef(workInProgress: Fiber) {
- workInProgress.flags |= Ref | RefStatic;
-}
-
/**
* In persistent mode, return whether this update needs to clone the subtree.
*/
@@ -1083,9 +1077,6 @@ function completeWork(
// @TODO refactor this block to create the instance here in complete
// phase if we are not hydrating.
markUpdate(workInProgress);
- if (workInProgress.ref !== null) {
- markRef(workInProgress);
- }
if (nextResource !== null) {
// This is a Hoistable Resource
@@ -1120,9 +1111,6 @@ function completeWork(
// and require an update
markUpdate(workInProgress);
}
- if (current.ref !== workInProgress.ref) {
- markRef(workInProgress);
- }
if (nextResource !== null) {
// This is a Hoistable Resource
// This must come at the very end of the complete phase.
@@ -1194,10 +1182,6 @@ function completeWork(
renderLanes,
);
}
-
- if (current.ref !== workInProgress.ref) {
- markRef(workInProgress);
- }
} else {
if (!newProps) {
if (workInProgress.stateNode === null) {
@@ -1232,11 +1216,6 @@ function completeWork(
workInProgress.stateNode = instance;
markUpdate(workInProgress);
}
-
- if (workInProgress.ref !== null) {
- // If there is a ref on a host node we need to schedule a callback
- markRef(workInProgress);
- }
}
bubbleProperties(workInProgress);
return null;
@@ -1254,10 +1233,6 @@ function completeWork(
newProps,
renderLanes,
);
-
- if (current.ref !== workInProgress.ref) {
- markRef(workInProgress);
- }
} else {
if (!newProps) {
if (workInProgress.stateNode === null) {
@@ -1310,11 +1285,6 @@ function completeWork(
markUpdate(workInProgress);
}
}
-
- if (workInProgress.ref !== null) {
- // If there is a ref on a host node we need to schedule a callback
- markRef(workInProgress);
- }
}
bubbleProperties(workInProgress);
@@ -1738,17 +1708,6 @@ function completeWork(
const scopeInstance: ReactScopeInstance = createScopeInstance();
workInProgress.stateNode = scopeInstance;
prepareScopeUpdate(scopeInstance, workInProgress);
- if (workInProgress.ref !== null) {
- markRef(workInProgress);
- markUpdate(workInProgress);
- }
- } else {
- if (workInProgress.ref !== null) {
- markUpdate(workInProgress);
- }
- if (current.ref !== workInProgress.ref) {
- markRef(workInProgress);
- }
}
bubbleProperties(workInProgress);
return null;
diff --git a/packages/react-reconciler/src/__tests__/ReactFiberRefs-test.js b/packages/react-reconciler/src/__tests__/ReactFiberRefs-test.js
new file mode 100644
index 0000000000000..a32b826597f22
--- /dev/null
+++ b/packages/react-reconciler/src/__tests__/ReactFiberRefs-test.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+let React;
+let Scheduler;
+let ReactNoop;
+let act;
+let assertLog;
+
+describe('ReactFiberRefs', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ React = require('react');
+ Scheduler = require('scheduler');
+ ReactNoop = require('react-noop-renderer');
+ act = require('internal-test-utils').act;
+ assertLog = require('internal-test-utils').assertLog;
+ });
+
+ test('ref is attached even if there are no other updates (class)', async () => {
+ let component;
+ class Component extends React.PureComponent {
+ render() {
+ Scheduler.log('Render');
+ component = this;
+ return 'Hi';
+ }
+ }
+
+ const ref1 = React.createRef();
+ const ref2 = React.createRef();
+ const root = ReactNoop.createRoot();
+
+ // Mount with ref1 attached
+ await act(() => root.render(