Remove string refs (behind flag) (#28322)
Depends on: - https://github.com/facebook/react/pull/28398 --- This removes string refs, which has been deprecated in Strict Mode for seven years. I've left them behind a flag for Meta, but in open source this fully removes the feature.
Andrew Clark committed
Feb 27, 2024 at 11:43 UTC
c9798954e26a2354a951cc65607f2901a45bf035
23 files changed
+154
-88
packages/react-dom/src/__tests__/ReactComponent-test.js
+2
@@ -38,6 +38,7 @@ describe('ReactComponent', () => {
38
}).toThrowError(/Target container is not a DOM element./);
39
});
40
41
+ // @gate !disableStringRefs || !__DEV__
42
it('should throw when supplying a string ref outside of render method', async () => {
43
const container = document.createElement('div');
44
const root = ReactDOMClient.createRoot(container);
@@ -125,6 +126,7 @@ describe('ReactComponent', () => {
126
}
127
});
128
129
+ // @gate !disableStringRefs
130
it('should support string refs on owned components', async () => {
131
const innerObj = {};
132
const outerObj = {};
packages/react-dom/src/__tests__/ReactDOMServerIntegrationRefs-test.js
+1
@@ -76,6 +76,7 @@ describe('ReactDOMServerIntegration', () => {
76
expect(refElement).toBe(e);
77
});
78
79
+ // @gate !disableStringRefs
80
it('should have string refs on client when rendered over server markup', async () => {
81
class RefsComponent extends React.Component {
82
render() {
packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js
+35
-32
@@ -64,6 +64,7 @@ describe('ReactDeprecationWarnings', () => {
64
);
65
});
66
67
+ // @gate !disableStringRefs
68
it('should warn when given string refs', async () => {
69
class RefComponent extends React.Component {
70
render() {
@@ -87,6 +88,7 @@ describe('ReactDeprecationWarnings', () => {
88
);
89
});
90
91
+ // @gate !disableStringRefs
92
it('should warn when owner and self are the same for string refs', async () => {
93
class RefComponent extends React.Component {
94
render() {
@@ -109,6 +111,7 @@ describe('ReactDeprecationWarnings', () => {
111
await waitForAll([]);
112
});
113
114
+ // @gate !disableStringRefs
115
it('should warn when owner and self are different for string refs', async () => {
116
class RefComponent extends React.Component {
117
render() {
@@ -139,39 +142,39 @@ describe('ReactDeprecationWarnings', () => {
142
]);
143
});
144
142
- if (__DEV__) {
143
- it('should warn when owner and self are different for string refs', async () => {
144
- class RefComponent extends React.Component {
145
- render() {
146
- return null;
147
- }
145
+ // @gate __DEV__
146
+ // @gate !disableStringRefs
147
+ it('should warn when owner and self are different for string refs', async () => {
148
+ class RefComponent extends React.Component {
149
+ render() {
150
+ return null;
151
}
149
- class Component extends React.Component {
150
- render() {
151
- return JSXDEVRuntime.jsxDEV(
152
- RefComponent,
153
- {ref: 'refComponent'},
154
- null,
155
- false,
156
- {},
157
- {},
158
- );
159
- }
152
+ }
153
+ class Component extends React.Component {
154
+ render() {
155
+ return JSXDEVRuntime.jsxDEV(
156
+ RefComponent,
157
+ {ref: 'refComponent'},
158
+ null,
159
+ false,
160
+ {},
161
+ {},
162
+ );
163
}
164
+ }
165
162
- ReactNoop.render(<Component />);
163
- await expect(async () => await waitForAll([])).toErrorDev([
164
- 'Warning: Component "Component" contains the string ref "refComponent". ' +
165
- 'Support for string refs will be removed in a future major release. ' +
166
- 'This case cannot be automatically converted to an arrow function. ' +
167
- 'We ask you to manually fix this case by using useRef() or createRef() instead. ' +
168
- 'Learn more about using refs safely here: ' +
169
- 'https://reactjs.org/link/strict-mode-string-ref',
170
- 'Warning: Component "Component" contains the string ref "refComponent". ' +
171
- 'Support for string refs will be removed in a future major release. We recommend ' +
172
- 'using useRef() or createRef() instead. Learn more about using refs safely here: ' +
173
- 'https://reactjs.org/link/strict-mode-string-ref',
174
- ]);
175
- });
176
- }
166
+ ReactNoop.render(<Component />);
167
+ await expect(async () => await waitForAll([])).toErrorDev([
168
+ 'Warning: Component "Component" contains the string ref "refComponent". ' +
169
+ 'Support for string refs will be removed in a future major release. ' +
170
+ 'This case cannot be automatically converted to an arrow function. ' +
171
+ 'We ask you to manually fix this case by using useRef() or createRef() instead. ' +
172
+ 'Learn more about using refs safely here: ' +
173
+ 'https://reactjs.org/link/strict-mode-string-ref',
174
+ 'Warning: Component "Component" contains the string ref "refComponent". ' +
175
+ 'Support for string refs will be removed in a future major release. We recommend ' +
176
+ 'using useRef() or createRef() instead. Learn more about using refs safely here: ' +
177
+ 'https://reactjs.org/link/strict-mode-string-ref',
178
+ ]);
179
+ });
180
});
packages/react-dom/src/__tests__/ReactFunctionComponent-test.js
+1
@@ -173,6 +173,7 @@ describe('ReactFunctionComponent', () => {
173
).resolves.not.toThrowError();
174
});
175
176
+ // @gate !disableStringRefs
177
it('should throw on string refs in pure functions', async () => {
178
function Child() {
179
return <div ref="me" />;
packages/react-dom/src/__tests__/multiple-copies-of-react-test.js
+1
@@ -22,6 +22,7 @@ class TextWithStringRef extends React.Component {
22
}
23
24
describe('when different React version is used with string ref', () => {
25
+ // @gate !disableStringRefs
26
it('throws the "Refs must have owner" warning', async () => {
27
const container = document.createElement('div');
28
const root = ReactDOMClient.createRoot(container);
packages/react-dom/src/__tests__/refs-test.js
+5
@@ -164,6 +164,7 @@ describe('reactiverefs', () => {
164
* Ensure that for every click log there is a corresponding ref (from the
165
* perspective of the injected ClickCounter component.
166
*/
167
+ // @gate !disableStringRefs
168
it('Should increase refs with an increase in divs', async () => {
169
const testRefsComponent = await renderTestRefsComponent();
170
const clickIncrementer =
@@ -366,6 +367,7 @@ describe('ref swapping', () => {
367
expect(refCalled).toBe(1);
368
});
369
370
+ // @gate !disableStringRefs
371
it('coerces numbers to strings', async () => {
372
class A extends React.Component {
373
render() {
@@ -390,6 +392,7 @@ describe('ref swapping', () => {
392
expect(a.refs[1].nodeName).toBe('DIV');
393
});
394
395
+ // @gate !disableStringRefs
396
it('provides an error for invalid refs', async () => {
397
const container = document.createElement('div');
398
const root = ReactDOMClient.createRoot(container);
@@ -547,6 +550,7 @@ describe('creating element with string ref in constructor', () => {
550
}
551
}
552
553
+ // @gate !disableStringRefs
554
it('throws an error', async () => {
555
await expect(async function () {
556
const container = document.createElement('div');
@@ -567,6 +571,7 @@ describe('creating element with string ref in constructor', () => {
571
});
572
573
describe('strings refs across renderers', () => {
574
+ // @gate !disableStringRefs
575
it('does not break', async () => {
576
class Parent extends React.Component {
577
render() {
packages/react-reconciler/src/ReactChildFiber.js
+2
-1
@@ -44,7 +44,7 @@ import {
44
import isArray from 'shared/isArray';
45
import assign from 'shared/assign';
46
import {checkPropStringCoercion} from 'shared/CheckStringCoercion';
47
-import {enableRefAsProp} from 'shared/ReactFeatureFlags';
47
+import {enableRefAsProp, disableStringRefs} from 'shared/ReactFeatureFlags';
48
49
import {
50
createWorkInProgress,
@@ -266,6 +266,7 @@ function coerceRef(
266
267
let coercedRef;
268
if (
269
+ !disableStringRefs &&
270
mixedRef !== null &&
271
typeof mixedRef !== 'function' &&
272
typeof mixedRef !== 'object'
packages/react-reconciler/src/ReactFiberCommitWork.js
+6
-1
@@ -54,6 +54,7 @@ import {
54
enableFloat,
55
enableLegacyHidden,
56
alwaysThrottleRetries,
57
+ disableStringRefs,
58
} from 'shared/ReactFeatureFlags';
59
import {
60
FunctionComponent,
@@ -1624,7 +1625,11 @@ function commitAttachRef(finishedWork: Fiber) {
1625
}
1626
} else {
1627
if (__DEV__) {
1627
- if (!ref.hasOwnProperty('current')) {
1628
+ // TODO: We should move these warnings to happen during the render
1629
+ // phase (markRef).
1630
+ if (disableStringRefs && typeof ref === 'string') {
1631
+ console.error('String refs are no longer supported.');
1632
+ } else if (!ref.hasOwnProperty('current')) {
1633
console.error(
1634
'Unexpected ref object provided for %s. ' +
1635
'Use either a ref-setter function or React.createRef().',
packages/react-reconciler/src/__tests__/ReactFiberRefs-test.js
+22
@@ -86,6 +86,7 @@ describe('ReactFiberRefs', () => {
86
});
87
88
// @gate enableRefAsProp
89
+ // @gate !disableStringRefs
90
test('string ref props are converted to function refs', async () => {
91
let refProp;
92
function Child({ref}) {
@@ -112,4 +113,25 @@ describe('ReactFiberRefs', () => {
113
expect(typeof refProp === 'function').toBe(true);
114
expect(owner.refs.child.type).toBe('div');
115
});
116
+
117
+ // @gate disableStringRefs
118
+ test('log an error in dev if a string ref is passed to a ref-receiving component', async () => {
119
+ let refProp;
120
+ function Child({ref}) {
121
+ refProp = ref;
122
+ return <div ref={ref} />;
123
+ }
124
+
125
+ class Owner extends React.Component {
126
+ render() {
127
+ return <Child ref="child" />;
128
+ }
129
+ }
130
+
131
+ const root = ReactNoop.createRoot();
132
+ await expect(async () => {
133
+ await expect(act(() => root.render(<Owner />))).rejects.toThrow();
134
+ }).toErrorDev('String refs are no longer supported');
135
+ expect(refProp).toBe('child');
136
+ });
137
});
packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.js
+1
@@ -1346,6 +1346,7 @@ describe('ReactIncrementalSideEffects', () => {
1346
// TODO: Test that mounts, updates, refs, unmounts and deletions happen in the
1347
// expected way for aborted and resumed render life-cycles.
1348
1349
+ // @gate !disableStringRefs
1350
it('supports string refs', async () => {
1351
let fooInstance = null;
1352
packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee
+19
-18
@@ -535,25 +535,26 @@ describe 'ReactCoffeeScriptClass', ->
535
536
test React.createElement(Foo), 'DIV', 'bar-through-context'
537
538
- it 'supports string refs', ->
539
- class Foo extends React.Component
540
- render: ->
541
- React.createElement(InnerComponent,
542
- name: 'foo'
543
- ref: 'inner'
544
- )
538
+ if !featureFlags.disableStringRefs
539
+ it 'supports string refs', ->
540
+ class Foo extends React.Component
541
+ render: ->
542
+ React.createElement(InnerComponent,
543
+ name: 'foo'
544
+ ref: 'inner'
545
+ )
546
546
- ref = React.createRef()
547
- expect(->
548
- test(React.createElement(Foo, ref: ref), 'DIV', 'foo')
549
- ).toErrorDev([
550
- 'Warning: Component "Foo" contains the string ref "inner". ' +
551
- 'Support for string refs will be removed in a future major release. ' +
552
- 'We recommend using useRef() or createRef() instead. ' +
553
- 'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
554
- ' in Foo (at **)'
555
- ]);
556
- expect(ref.current.refs.inner.getName()).toBe 'foo'
547
+ ref = React.createRef()
548
+ expect(->
549
+ test(React.createElement(Foo, ref: ref), 'DIV', 'foo')
550
+ ).toErrorDev([
551
+ 'Warning: Component "Foo" contains the string ref "inner". ' +
552
+ 'Support for string refs will be removed in a future major release. ' +
553
+ 'We recommend using useRef() or createRef() instead. ' +
554
+ 'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
555
+ ' in Foo (at **)'
556
+ ]);
557
+ expect(ref.current.refs.inner.getName()).toBe 'foo'
558
559
it 'supports drilling through to the DOM using findDOMNode', ->
560
ref = React.createRef()
packages/react/src/__tests__/ReactES6Class-test.js
+19
-17
@@ -576,24 +576,26 @@ describe('ReactES6Class', () => {
576
});
577
}
578
579
- it('supports string refs', () => {
580
- class Foo extends React.Component {
581
- render() {
582
- return <Inner name="foo" ref="inner" />;
579
+ if (!require('shared/ReactFeatureFlags').disableStringRefs) {
580
+ it('supports string refs', () => {
581
+ class Foo extends React.Component {
582
+ render() {
583
+ return <Inner name="foo" ref="inner" />;
584
+ }
585
}
584
- }
585
- const ref = React.createRef();
586
- expect(() => {
587
- test(<Foo ref={ref} />, 'DIV', 'foo');
588
- }).toErrorDev([
589
- 'Warning: Component "Foo" contains the string ref "inner". ' +
590
- 'Support for string refs will be removed in a future major release. ' +
591
- 'We recommend using useRef() or createRef() instead. ' +
592
- 'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
593
- ' in Foo (at **)',
594
- ]);
595
- expect(ref.current.refs.inner.getName()).toBe('foo');
596
- });
586
+ const ref = React.createRef();
587
+ expect(() => {
588
+ test(<Foo ref={ref} />, 'DIV', 'foo');
589
+ }).toErrorDev([
590
+ 'Warning: Component "Foo" contains the string ref "inner". ' +
591
+ 'Support for string refs will be removed in a future major release. ' +
592
+ 'We recommend using useRef() or createRef() instead. ' +
593
+ 'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
594
+ ' in Foo (at **)',
595
+ ]);
596
+ expect(ref.current.refs.inner.getName()).toBe('foo');
597
+ });
598
+ }
599
600
it('supports drilling through to the DOM using findDOMNode', () => {
601
const ref = React.createRef();
packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js
+7
-3
@@ -118,13 +118,17 @@ describe('ReactProfiler DevTools integration', () => {
118
119
Scheduler.unstable_advanceTime(20);
120
121
+ function Throws() {
122
+ throw new Error('Oops!');
123
+ }
124
+
125
expect(() => {
126
rendered.update(
123
- <div ref="this-will-cause-an-error">
127
+ <Throws>
128
<AdvanceTime byAmount={3} />
125
- </div>,
129
+ </Throws>,
130
);
127
- }).toThrow();
131
+ }).toThrow('Oops!');
132
133
Scheduler.unstable_advanceTime(20);
134
packages/react/src/__tests__/ReactStrictMode-test.js
+2
@@ -968,6 +968,7 @@ describe('string refs', () => {
968
act = require('internal-test-utils').act;
969
});
970
971
+ // @gate !disableStringRefs
972
it('should warn within a strict tree', async () => {
973
const {StrictMode} = React;
974
@@ -1006,6 +1007,7 @@ describe('string refs', () => {
1007
});
1008
});
1009
1010
+ // @gate !disableStringRefs
1011
it('should warn within a strict tree', async () => {
1012
const {StrictMode} = React;
1013
packages/react/src/__tests__/ReactTypeScriptClass-test.ts
+15
-13
@@ -689,19 +689,21 @@ describe('ReactTypeScriptClass', function() {
689
});
690
}
691
692
- it('supports string refs', function() {
693
- const ref = React.createRef();
694
- expect(() => {
695
- test(React.createElement(ClassicRefs, {ref: ref}), 'DIV', 'foo');
696
- }).toErrorDev([
697
- 'Warning: Component "ClassicRefs" contains the string ref "inner". ' +
698
- 'Support for string refs will be removed in a future major release. ' +
699
- 'We recommend using useRef() or createRef() instead. ' +
700
- 'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
701
- ' in ClassicRefs (at **)',
702
- ]);
703
- expect(ref.current.refs.inner.getName()).toBe('foo');
704
- });
692
+ if (!ReactFeatureFlags.disableStringRefs) {
693
+ it('supports string refs', function() {
694
+ const ref = React.createRef();
695
+ expect(() => {
696
+ test(React.createElement(ClassicRefs, {ref: ref}), 'DIV', 'foo');
697
+ }).toErrorDev([
698
+ 'Warning: Component "ClassicRefs" contains the string ref "inner". ' +
699
+ 'Support for string refs will be removed in a future major release. ' +
700
+ 'We recommend using useRef() or createRef() instead. ' +
701
+ 'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
702
+ ' in ClassicRefs (at **)',
703
+ ]);
704
+ expect(ref.current.refs.inner.getName()).toBe('foo');
705
+ });
706
+ }
707
708
it('supports drilling through to the DOM using findDOMNode', function() {
709
const ref = React.createRef();
packages/react/src/jsx/ReactJSXElement.js
+6
-3
@@ -18,7 +18,7 @@ import {checkKeyStringCoercion} from 'shared/CheckStringCoercion';
18
import isValidElementType from 'shared/isValidElementType';
19
import isArray from 'shared/isArray';
20
import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame';
21
-import {enableRefAsProp} from 'shared/ReactFeatureFlags';
21
+import {enableRefAsProp, disableStringRefs} from 'shared/ReactFeatureFlags';
22
23
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
24
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
@@ -62,6 +62,7 @@ function hasValidKey(config) {
62
function warnIfStringRefCannotBeAutoConverted(config, self) {
63
if (__DEV__) {
64
if (
65
+ !disableStringRefs &&
66
typeof config.ref === 'string' &&
67
ReactCurrentOwner.current &&
68
self &&
@@ -536,7 +537,9 @@ export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) {
537
if (!enableRefAsProp) {
538
ref = config.ref;
539
}
539
- warnIfStringRefCannotBeAutoConverted(config, self);
540
+ if (!disableStringRefs) {
541
+ warnIfStringRefCannotBeAutoConverted(config, self);
542
+ }
543
}
544
545
// Remaining properties are added to a new props object
@@ -665,7 +668,7 @@ export function createElement(type, config, children) {
668
ref = config.ref;
669
}
670
668
- if (__DEV__) {
671
+ if (__DEV__ && !disableStringRefs) {
672
warnIfStringRefCannotBeAutoConverted(config, config.__self);
673
}
674
}
packages/shared/ReactFeatureFlags.js
+1
@@ -188,6 +188,7 @@ export const enableInfiniteRenderLoopDetection = true;
188
// Passes `ref` as a normal prop instead of stripping it from the props object
189
// during element creation.
190
export const enableRefAsProp = __NEXT_MAJOR__;
191
+export const disableStringRefs = __NEXT_MAJOR__;
192
193
// Not ready to break experimental yet.
194
// Needs more internal cleanup
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -101,6 +101,7 @@ export const enableInfiniteRenderLoopDetection = false;
101
// TODO: Roll out with GK. Don't keep as dynamic flag for too long, though,
102
// because JSX is an extremely hot path.
103
export const enableRefAsProp = false;
104
+export const disableStringRefs = false;
105
106
export const enableReactTestRendererWarning = false;
107
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -92,6 +92,7 @@ export const enableServerComponentLogs = true;
92
93
// TODO: Should turn this on in next "major" RN release.
94
export const enableRefAsProp = false;
95
+export const disableStringRefs = false;
96
97
export const enableReactTestRendererWarning = false;
98
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -98,6 +98,7 @@ export const enableInfiniteRenderLoopDetection = false;
98
// flags should be handled by the Fiber config.
99
const __NEXT_MAJOR__ = __EXPERIMENTAL__;
100
export const enableRefAsProp = __NEXT_MAJOR__;
101
+export const disableStringRefs = __NEXT_MAJOR__;
102
export const enableReactTestRendererWarning = false;
103
export const enableBigIntSupport = __NEXT_MAJOR__;
104
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+1
@@ -88,6 +88,7 @@ export const enableServerComponentKeys = true;
88
export const enableServerComponentLogs = true;
89
90
export const enableRefAsProp = false;
91
+export const disableStringRefs = false;
92
93
export const enableReactTestRendererWarning = false;
94
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -91,6 +91,7 @@ export const enableServerComponentLogs = true;
91
export const enableInfiniteRenderLoopDetection = false;
92
93
export const enableRefAsProp = false;
94
+export const disableStringRefs = false;
95
96
export const enableReactTestRendererWarning = false;
97
packages/shared/forks/ReactFeatureFlags.www.js
+4
@@ -121,5 +121,9 @@ export const enableReactTestRendererWarning = false;
121
122
export const enableBigIntSupport = false;
123
124
+// TODO: Roll out with GK. Don't keep as dynamic flag for too long, though,
125
+// because JSX is an extremely hot path.
126
+export const disableStringRefs = false;
127
+
128
// Flow magic to verify the exports of this file match the original version.
129
((((null: any): ExportsType): FeatureFlagsType): ExportsType);