Skip to content

Commit 773bb4a

Browse files
committed
Support writing to this.refs from userspace
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.
1 parent 0061ca6 commit 773bb4a

File tree

3 files changed

+30
-13
lines changed

3 files changed

+30
-13
lines changed

packages/react-reconciler/src/ReactFiberClassComponent.js

-1
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,6 @@ function mountClassInstance(
819819
const instance = workInProgress.stateNode;
820820
instance.props = newProps;
821821
instance.state = workInProgress.memoizedState;
822-
instance.refs = {};
823822

824823
initializeUpdateQueue(workInProgress);
825824

packages/react-reconciler/src/__tests__/ReactFiberRefs-test.js

+24
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,28 @@ describe('ReactFiberRefs', () => {
138138
);
139139
expect(refProp).toBe('child');
140140
});
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+
});
141165
});

packages/react/src/ReactBaseClasses.js

+6-12
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,13 @@
88
import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';
99
import assign from 'shared/assign';
1010

11-
const emptyObject = {};
12-
if (__DEV__) {
13-
Object.freeze(emptyObject);
14-
}
15-
1611
/**
1712
* Base class helpers for the updating state of a component.
1813
*/
1914
function Component(props, context, updater) {
2015
this.props = props;
2116
this.context = context;
22-
// If a component has string refs, we will assign a different object later.
23-
this.refs = emptyObject;
17+
this.refs = {};
2418
// We initialize the default updater but the real one gets injected by the
2519
// renderer.
2620
this.updater = updater || ReactNoopUpdateQueue;
@@ -53,7 +47,7 @@ Component.prototype.isReactComponent = {};
5347
* @final
5448
* @protected
5549
*/
56-
Component.prototype.setState = function (partialState, callback) {
50+
Component.prototype.setState = function(partialState, callback) {
5751
if (
5852
typeof partialState !== 'object' &&
5953
typeof partialState !== 'function' &&
@@ -82,7 +76,7 @@ Component.prototype.setState = function (partialState, callback) {
8276
* @final
8377
* @protected
8478
*/
85-
Component.prototype.forceUpdate = function (callback) {
79+
Component.prototype.forceUpdate = function(callback) {
8680
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
8781
};
8882

@@ -104,9 +98,9 @@ if (__DEV__) {
10498
'https://github.com/facebook/react/issues/3236).',
10599
],
106100
};
107-
const defineDeprecationWarning = function (methodName, info) {
101+
const defineDeprecationWarning = function(methodName, info) {
108102
Object.defineProperty(Component.prototype, methodName, {
109-
get: function () {
103+
get: function() {
110104
console.warn(
111105
'%s(...) is deprecated in plain JavaScript React classes. %s',
112106
info[0],
@@ -133,7 +127,7 @@ function PureComponent(props, context, updater) {
133127
this.props = props;
134128
this.context = context;
135129
// If a component has string refs, we will assign a different object later.
136-
this.refs = emptyObject;
130+
this.refs = {};
137131
this.updater = updater || ReactNoopUpdateQueue;
138132
}
139133

0 commit comments

Comments
 (0)