Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix cloneElement using string ref w no owner #28797

Merged
merged 8 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions packages/react/src/__tests__/ReactElementClone-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,56 @@ describe('ReactElementClone', () => {

const root = ReactDOMClient.createRoot(document.createElement('div'));
await act(() => root.render(<Grandparent />));
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');
Comment on lines +286 to +289
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm pretty sure this isn't running...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, not by default. thanks CI

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, it's getting hit, you have to run with the www variant:

yarn test-www --variant=true

Tested locally:

Screenshot 2024-04-09 at 3 01 39 PM

} 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 <Child>{element}</Child>;
}
}
let child;
class Child extends React.Component {
render() {
child = this;
const clone = React.cloneElement(this.props.children, {
ref: 'xyz',
});
return <div>{clone}</div>;
}
}

const root = ReactDOMClient.createRoot(document.createElement('div'));
await act(() => root.render(<Parent />));
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 () => {
Expand Down Expand Up @@ -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});
Copy link
Member

@rickhanlonii rickhanlonii Apr 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josephsavona since enableRefAsProp is on, the props will include the ref, so I updated the test.

} else {
// Not going to bother testing every possible combination.
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/jsx/ReactJSXElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -866,14 +866,14 @@ export function cloneElement(element, config, children) {

if (config != null) {
if (hasValidRef(config)) {
owner = ReactSharedInternals.owner;
if (!enableRefAsProp) {
// Silently steal the ref from the parent.
ref = config.ref;
if (!disableStringRefs) {
ref = coerceStringRef(ref, owner, element.type);
}
}
owner = ReactSharedInternals.owner;
}
if (hasValidKey(config)) {
if (__DEV__) {
Expand Down