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.
Andrew Clark committed
Apr 18, 2024 at 13:37 UTC
ea24427d16f3ac9b0f3bb45cdc7919ac208130c9
3 files changed
+26
-9
packages/react-reconciler/src/ReactFiberClassComponent.js
-1
@@ -819,7 +819,6 @@ function mountClassInstance(
819
const instance = workInProgress.stateNode;
820
instance.props = newProps;
821
instance.state = workInProgress.memoizedState;
822
- instance.refs = {};
822
823
initializeUpdateQueue(workInProgress);
824
packages/react-reconciler/src/__tests__/ReactFiberRefs-test.js
+24
@@ -138,4 +138,28 @@ describe('ReactFiberRefs', () => {
138
);
139
expect(refProp).toBe('child');
140
});
141
+
142
+ test('strings refs can be codemodded to callback refs', async () => {
143
+ let app;
144
+ class App extends React.Component {
145
+ render() {
146
+ app = this;
147
+ return (
148
+ <div
149
+ prop="Hello!"
150
+ ref={el => {
151
+ // `refs` used to be a shared frozen object unless/until a string
152
+ // ref attached by the reconciler, but it's not anymore so that we
153
+ // can codemod string refs to userspace callback refs.
154
+ this.refs.div = el;
155
+ }}
156
+ />
157
+ );
158
+ }
159
+ }
160
+
161
+ const root = ReactNoop.createRoot();
162
+ await act(() => root.render(<App />));
163
+ expect(app.refs.div.prop).toBe('Hello!');
164
+ });
165
});
packages/react/src/ReactBaseClasses.js
+2
-8
@@ -8,19 +8,13 @@
8
import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';
9
import assign from 'shared/assign';
10
11
-const emptyObject = {};
12
-if (__DEV__) {
13
- Object.freeze(emptyObject);
14
-}
15
-
11
/**
12
* Base class helpers for the updating state of a component.
13
*/
14
function Component(props, context, updater) {
15
this.props = props;
16
this.context = context;
22
- // If a component has string refs, we will assign a different object later.
23
- this.refs = emptyObject;
17
+ this.refs = {};
18
// We initialize the default updater but the real one gets injected by the
19
// renderer.
20
this.updater = updater || ReactNoopUpdateQueue;
@@ -133,7 +127,7 @@ function PureComponent(props, context, updater) {
127
this.props = props;
128
this.context = context;
129
// If a component has string refs, we will assign a different object later.
136
- this.refs = emptyObject;
130
+ this.refs = {};
131
this.updater = updater || ReactNoopUpdateQueue;
132
}
133