add warning when owner and self are different for string refs (#17864)
When owner and self are different for string refs, we can't easily convert them to callback refs. This PR adds a warning for string refs when owner and self are different to tell users to manually update these refs.
Luna Ruan committed
Jan 22, 2020 at 12:32 UTC
3b3decf87121dcc20bfc0820b7b35d2b028adaf2
3 files changed
+91
-2
packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.internal.js
+40
@@ -69,4 +69,44 @@ describe('ReactDeprecationWarnings', () => {
69
'\n in Component (at **)',
70
);
71
});
72
+
73
+ it('should not warn when owner and self are the same for string refs', () => {
74
+ ReactFeatureFlags.warnAboutStringRefs = false;
75
+
76
+ class RefComponent extends React.Component {
77
+ render() {
78
+ return null;
79
+ }
80
+ }
81
+ class Component extends React.Component {
82
+ render() {
83
+ return <RefComponent ref="refComponent" __self={this} />;
84
+ }
85
+ }
86
+ ReactNoop.renderLegacySyncRoot(<Component />);
87
+ expect(Scheduler).toFlushWithoutYielding();
88
+ });
89
+
90
+ it('should warn when owner and self are different for string refs', () => {
91
+ class RefComponent extends React.Component {
92
+ render() {
93
+ return null;
94
+ }
95
+ }
96
+ class Component extends React.Component {
97
+ render() {
98
+ return <RefComponent ref="refComponent" __self={{}} />;
99
+ }
100
+ }
101
+
102
+ ReactNoop.render(<Component />);
103
+ expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev([
104
+ 'Warning: Component "Component" contains the string ref "refComponent". ' +
105
+ 'Support for string refs will be removed in a future major release. ' +
106
+ 'This case cannot be automatically converted to an arrow function. ' +
107
+ 'We ask you to manually fix this case by using useRef() or createRef() instead. ' +
108
+ 'Learn more about using refs safely here: ' +
109
+ 'https://fb.me/react-strict-mode-string-ref',
110
+ ]);
111
+ });
112
});
packages/react-reconciler/src/ReactChildFiber.js
+11
-1
@@ -116,7 +116,17 @@ function coerceRef(
116
if (__DEV__) {
117
// TODO: Clean this up once we turn on the string ref warning for
118
// everyone, because the strict mode case will no longer be relevant
119
- if (returnFiber.mode & StrictMode || warnAboutStringRefs) {
119
+ if (
120
+ (returnFiber.mode & StrictMode || warnAboutStringRefs) &&
121
+ // We warn in ReactElement.js if owner and self are equal for string refs
122
+ // because these cannot be automatically converted to an arrow function
123
+ // using a codemod. Therefore, we don't have to warn about string refs again.
124
+ !(
125
+ element._owner &&
126
+ element._self &&
127
+ element._owner.stateNode !== element._self
128
+ )
129
+ ) {
130
const componentName = getComponentName(returnFiber.type) || 'Component';
131
if (!didWarnAboutStringRefs[componentName]) {
132
if (warnAboutStringRefs) {
packages/react/src/ReactElement.js
+40
-1
@@ -5,6 +5,7 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
+import getComponentName from 'shared/getComponentName';
9
import invariant from 'shared/invariant';
10
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
11
@@ -19,7 +20,13 @@ const RESERVED_PROPS = {
20
__source: true,
21
};
22
22
-let specialPropKeyWarningShown, specialPropRefWarningShown;
23
+let specialPropKeyWarningShown,
24
+ specialPropRefWarningShown,
25
+ didWarnAboutStringRefs;
26
+
27
+if (__DEV__) {
28
+ didWarnAboutStringRefs = {};
29
+}
30
31
function hasValidRef(config) {
32
if (__DEV__) {
@@ -89,6 +96,33 @@ function defineRefPropWarningGetter(props, displayName) {
96
});
97
}
98
99
+function warnIfStringRefCannotBeAutoConverted(config) {
100
+ if (__DEV__) {
101
+ if (
102
+ typeof config.ref === 'string' &&
103
+ ReactCurrentOwner.current &&
104
+ config.__self &&
105
+ ReactCurrentOwner.current.stateNode !== config.__self
106
+ ) {
107
+ const componentName = getComponentName(ReactCurrentOwner.current.type);
108
+
109
+ if (!didWarnAboutStringRefs[componentName]) {
110
+ console.error(
111
+ 'Component "%s" contains the string ref "%s". ' +
112
+ 'Support for string refs will be removed in a future major release. ' +
113
+ 'This case cannot be automatically converted to an arrow function. ' +
114
+ 'We ask you to manually fix this case by using useRef() or createRef() instead. ' +
115
+ 'Learn more about using refs safely here: ' +
116
+ 'https://fb.me/react-strict-mode-string-ref',
117
+ getComponentName(ReactCurrentOwner.current.type),
118
+ config.ref,
119
+ );
120
+ didWarnAboutStringRefs[componentName] = true;
121
+ }
122
+ }
123
+ }
124
+}
125
+
126
/**
127
* Factory method to create a new React element. This no longer adheres to
128
* the class pattern, so do not use new to call it. Also, instanceof check
@@ -260,6 +294,7 @@ export function jsxDEV(type, config, maybeKey, source, self) {
294
295
if (hasValidRef(config)) {
296
ref = config.ref;
297
+ warnIfStringRefCannotBeAutoConverted(config);
298
}
299
300
// Remaining properties are added to a new props object
@@ -324,6 +359,10 @@ export function createElement(type, config, children) {
359
if (config != null) {
360
if (hasValidRef(config)) {
361
ref = config.ref;
362
+
363
+ if (__DEV__) {
364
+ warnIfStringRefCannotBeAutoConverted(config);
365
+ }
366
}
367
if (hasValidKey(config)) {
368
key = '' + config.key;