Support writing to this.refs from userspace (#28867)

Previously, the `refs` property of a class component instance was
read-only by user code — only React could write to it, and until/unless
a string ref was used, it pointed to a shared empty object that was
frozen in dev to prevent userspace mutations.

Because string refs are deprecated, we want users to be able to codemod
all their string refs to callback refs. The safest way to do this is to
output a callback ref that assigns to `this.refs`.

So to support this, we need to make `this.refs` writable by userspace.
This commit is contained in:
Andrew Clark
2024-04-18 13:37:54 -04:00
committed by GitHub
parent 0061ca6cf4
commit ea24427d16
3 changed files with 26 additions and 9 deletions
@@ -819,7 +819,6 @@ function mountClassInstance(
const instance = workInProgress.stateNode;
instance.props = newProps;
instance.state = workInProgress.memoizedState;
instance.refs = {};
initializeUpdateQueue(workInProgress);
@@ -138,4 +138,28 @@ describe('ReactFiberRefs', () => {
);
expect(refProp).toBe('child');
});
test('strings refs can be codemodded to callback refs', async () => {
let app;
class App extends React.Component {
render() {
app = this;
return (
<div
prop="Hello!"
ref={el => {
// `refs` used to be a shared frozen object unless/until a string
// ref attached by the reconciler, but it's not anymore so that we
// can codemod string refs to userspace callback refs.
this.refs.div = el;
}}
/>
);
}
}
const root = ReactNoop.createRoot();
await act(() => root.render(<App />));
expect(app.refs.div.prop).toBe('Hello!');
});
});
+2 -8
View File
@@ -8,19 +8,13 @@
import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';
import assign from 'shared/assign';
const emptyObject = {};
if (__DEV__) {
Object.freeze(emptyObject);
}
/**
* Base class helpers for the updating state of a component.
*/
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
this.refs = {};
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
@@ -133,7 +127,7 @@ function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
this.refs = {};
this.updater = updater || ReactNoopUpdateQueue;
}