@samitouri / QOS-React / commits / c9798954e2

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);