diff --git a/packages/react/src/__tests__/ReactElementClone-test.js b/packages/react/src/__tests__/ReactElementClone-test.js
index 11bb847518e41..a8cf751635149 100644
--- a/packages/react/src/__tests__/ReactElementClone-test.js
+++ b/packages/react/src/__tests__/ReactElementClone-test.js
@@ -274,8 +274,56 @@ describe('ReactElementClone', () => {
const root = ReactDOMClient.createRoot(document.createElement('div'));
await act(() => root.render());
- expect(component.childRef).toEqual({current: null});
- expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
+ if (gate(flags => flags.enableRefAsProp && flags.disableStringRefs)) {
+ expect(component.childRef).toEqual({current: null});
+ expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
+ } else if (
+ gate(flags => !flags.enableRefAsProp && !flags.disableStringRefs)
+ ) {
+ expect(component.childRef).toEqual({current: null});
+ expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
+ } else if (
+ gate(flags => flags.enableRefAsProp && !flags.disableStringRefs)
+ ) {
+ expect(component.childRef).toEqual({current: null});
+ expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
+ } else {
+ // Not going to bother testing every possible combination.
+ }
+ });
+
+ // @gate !disableStringRefs
+ it('should steal the ref if a new string ref is specified without an owner', async () => {
+ // Regression test for this specific feature combination calling cloneElement on an element
+ // without an owner
+ await expect(async () => {
+ // create an element without an owner
+ const element = React.createElement('div', {id: 'some-id'});
+ class Parent extends React.Component {
+ render() {
+ return {element};
+ }
+ }
+ let child;
+ class Child extends React.Component {
+ render() {
+ child = this;
+ const clone = React.cloneElement(this.props.children, {
+ ref: 'xyz',
+ });
+ return
{clone}
;
+ }
+ }
+
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => root.render());
+ expect(child.refs.xyz.tagName).toBe('DIV');
+ }).toErrorDev([
+ 'Warning: Component "Child" contains the string ref "xyz". Support for ' +
+ 'string refs will be removed in a future major release. We recommend ' +
+ 'using useRef() or createRef() instead. Learn more about using refs ' +
+ 'safely here: https://react.dev/link/strict-mode-string-ref',
+ ]);
});
it('should overwrite props', async () => {
@@ -371,6 +419,15 @@ describe('ReactElementClone', () => {
) {
expect(clone.ref).toBe(element.ref);
expect(clone.props).toEqual({foo: 'ef'});
+ } else if (
+ gate(flags => flags.enableRefAsProp && !flags.disableStringRefs)
+ ) {
+ expect(() => {
+ expect(clone.ref).toBe(element.ref);
+ }).toErrorDev('Accessing element.ref was removed in React 19', {
+ withoutStack: true,
+ });
+ expect(clone.props).toEqual({foo: 'ef', ref: element.ref});
} else {
// Not going to bother testing every possible combination.
}
diff --git a/packages/react/src/jsx/ReactJSXElement.js b/packages/react/src/jsx/ReactJSXElement.js
index 6727626b4e5d4..e684af6ef8ce9 100644
--- a/packages/react/src/jsx/ReactJSXElement.js
+++ b/packages/react/src/jsx/ReactJSXElement.js
@@ -853,6 +853,7 @@ export function cloneElement(element, config, children) {
if (config != null) {
if (hasValidRef(config)) {
+ owner = ReactCurrentOwner.owner;
if (!enableRefAsProp) {
// Silently steal the ref from the parent.
ref = config.ref;
@@ -860,7 +861,6 @@ export function cloneElement(element, config, children) {
ref = coerceStringRef(ref, owner, element.type);
}
}
- owner = ReactCurrentOwner.current;
}
if (hasValidKey(config)) {
if (__DEV__) {