Deprecate findDOMNode in StrictMode (#13841)
* Deprecate findDOMNode in StrictMode There are two scenarios. One is that we pass a component instance that is already in strict mode or the node that we find is in strict mode if an outer component renders into strict mode. I use a separate method findHostInstanceWithWarning for this so that a) I can pass the method name (findDOMNode/findNodeHandle). b) Can ignore this warning in React Native mixins/NativeComponent that use this helper. I don't want to expose the fiber to the renderers themselves.
Sebastian Markbåge committed
Oct 12, 2018 at 15:42 UTC
4773fdf7cdf5d6d775ad0960f23ee1a830e7b82b
9 files changed
+344
-11
packages/react-dom/src/__tests__/findDOMNode-test.js
+67
-1
@@ -12,6 +12,7 @@
12
const React = require('react');
13
const ReactDOM = require('react-dom');
14
const ReactTestUtils = require('react-dom/test-utils');
15
+const StrictMode = React.StrictMode;
16
17
describe('findDOMNode', () => {
18
it('findDOMNode should return null if passed null', () => {
@@ -94,7 +95,72 @@ describe('findDOMNode', () => {
95
return <div />;
96
}
97
}
97
-
98
expect(() => ReactTestUtils.renderIntoDocument(<Bar />)).not.toThrow();
99
});
100
+
101
+ it('findDOMNode should warn if used to find a host component inside StrictMode', () => {
102
+ let parent = undefined;
103
+ let child = undefined;
104
+
105
+ class ContainsStrictModeChild extends React.Component {
106
+ render() {
107
+ return (
108
+ <StrictMode>
109
+ <div ref={n => (child = n)} />
110
+ </StrictMode>
111
+ );
112
+ }
113
+ }
114
+
115
+ ReactTestUtils.renderIntoDocument(
116
+ <ContainsStrictModeChild ref={n => (parent = n)} />,
117
+ );
118
+
119
+ let match;
120
+ expect(() => (match = ReactDOM.findDOMNode(parent))).toWarnDev([
121
+ 'Warning: findDOMNode is deprecated in StrictMode. ' +
122
+ 'findDOMNode was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' +
123
+ 'Instead, add a ref directly to the element you want to reference.' +
124
+ '\n' +
125
+ '\n in div (at **)' +
126
+ '\n in StrictMode (at **)' +
127
+ '\n in ContainsStrictModeChild (at **)' +
128
+ '\n' +
129
+ '\nLearn more about using refs safely here:' +
130
+ '\nhttps://fb.me/react-strict-mode-find-node',
131
+ ]);
132
+ expect(match).toBe(child);
133
+ });
134
+
135
+ it('findDOMNode should warn if passed a component that is inside StrictMode', () => {
136
+ let parent = undefined;
137
+ let child = undefined;
138
+
139
+ class IsInStrictMode extends React.Component {
140
+ render() {
141
+ return <div ref={n => (child = n)} />;
142
+ }
143
+ }
144
+
145
+ ReactTestUtils.renderIntoDocument(
146
+ <StrictMode>
147
+ <IsInStrictMode ref={n => (parent = n)} />
148
+ </StrictMode>,
149
+ );
150
+
151
+ let match;
152
+ expect(() => (match = ReactDOM.findDOMNode(parent))).toWarnDev([
153
+ 'Warning: findDOMNode is deprecated in StrictMode. ' +
154
+ 'findDOMNode was passed an instance of IsInStrictMode which is inside StrictMode. ' +
155
+ 'Instead, add a ref directly to the element you want to reference.' +
156
+ '\n' +
157
+ '\n in div (at **)' +
158
+ '\n in IsInStrictMode (at **)' +
159
+ '\n in StrictMode (at **)' +
160
+ '\n' +
161
+ '\nLearn more about using refs safely here:' +
162
+ '\nhttps://fb.me/react-strict-mode-find-node',
163
+ ]);
164
+ expect(match).toBe(child);
165
+ });
166
});
packages/react-dom/src/client/ReactDOM.js
+6
-1
@@ -614,7 +614,12 @@ const ReactDOM: Object = {
614
if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
615
return (componentOrElement: any);
616
}
617
-
617
+ if (__DEV__) {
618
+ return DOMRenderer.findHostInstanceWithWarning(
619
+ componentOrElement,
620
+ 'findDOMNode',
621
+ );
622
+ }
623
return DOMRenderer.findHostInstance(componentOrElement);
624
},
625
packages/react-native-renderer/src/ReactFabric.js
+12
-1
@@ -29,6 +29,8 @@ import warningWithoutStack from 'shared/warningWithoutStack';
29
30
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
31
const findHostInstance = ReactFabricRenderer.findHostInstance;
32
+const findHostInstanceWithWarning =
33
+ ReactFabricRenderer.findHostInstanceWithWarning;
34
35
function findNodeHandle(componentOrHandle: any): ?number {
36
if (__DEV__) {
@@ -60,7 +62,16 @@ function findNodeHandle(componentOrHandle: any): ?number {
62
if (componentOrHandle.canonical && componentOrHandle.canonical._nativeTag) {
63
return componentOrHandle.canonical._nativeTag;
64
}
63
- const hostInstance = findHostInstance(componentOrHandle);
65
+ let hostInstance;
66
+ if (__DEV__) {
67
+ hostInstance = findHostInstanceWithWarning(
68
+ componentOrHandle,
69
+ 'findNodeHandle',
70
+ );
71
+ } else {
72
+ hostInstance = findHostInstance(componentOrHandle);
73
+ }
74
+
75
if (hostInstance == null) {
76
return hostInstance;
77
}
packages/react-native-renderer/src/ReactNativeRenderer.js
+12
-1
@@ -32,6 +32,8 @@ import warningWithoutStack from 'shared/warningWithoutStack';
32
33
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
34
const findHostInstance = ReactNativeFiberRenderer.findHostInstance;
35
+const findHostInstanceWithWarning =
36
+ ReactNativeFiberRenderer.findHostInstanceWithWarning;
37
38
function findNodeHandle(componentOrHandle: any): ?number {
39
if (__DEV__) {
@@ -63,7 +65,16 @@ function findNodeHandle(componentOrHandle: any): ?number {
65
if (componentOrHandle.canonical && componentOrHandle.canonical._nativeTag) {
66
return componentOrHandle.canonical._nativeTag;
67
}
66
- const hostInstance = findHostInstance(componentOrHandle);
68
+ let hostInstance;
69
+ if (__DEV__) {
70
+ hostInstance = findHostInstanceWithWarning(
71
+ componentOrHandle,
72
+ 'findNodeHandle',
73
+ );
74
+ } else {
75
+ hostInstance = findHostInstance(componentOrHandle);
76
+ }
77
+
78
if (hostInstance == null) {
79
return hostInstance;
80
}
packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
+77
@@ -15,6 +15,7 @@ let ReactFabric;
15
let createReactNativeComponentClass;
16
let UIManager;
17
let FabricUIManager;
18
+let StrictMode;
19
20
jest.mock('shared/ReactFeatureFlags', () =>
21
require('shared/forks/ReactFeatureFlags.native-fabric-oss'),
@@ -25,6 +26,7 @@ describe('ReactFabric', () => {
26
jest.resetModules();
27
28
React = require('react');
29
+ StrictMode = React.StrictMode;
30
ReactFabric = require('react-native-renderer/fabric');
31
FabricUIManager = require('FabricUIManager');
32
UIManager = require('UIManager');
@@ -436,4 +438,79 @@ describe('ReactFabric', () => {
438
// This could change in the future.
439
expect(touchStart2).toBeCalled();
440
});
441
+
442
+ it('findNodeHandle should warn if used to find a host component inside StrictMode', () => {
443
+ const View = createReactNativeComponentClass('RCTView', () => ({
444
+ validAttributes: {foo: true},
445
+ uiViewClassName: 'RCTView',
446
+ }));
447
+
448
+ let parent = undefined;
449
+ let child = undefined;
450
+
451
+ class ContainsStrictModeChild extends React.Component {
452
+ render() {
453
+ return (
454
+ <StrictMode>
455
+ <View ref={n => (child = n)} />
456
+ </StrictMode>
457
+ );
458
+ }
459
+ }
460
+
461
+ ReactFabric.render(<ContainsStrictModeChild ref={n => (parent = n)} />, 11);
462
+
463
+ let match;
464
+ expect(() => (match = ReactFabric.findNodeHandle(parent))).toWarnDev([
465
+ 'Warning: findNodeHandle is deprecated in StrictMode. ' +
466
+ 'findNodeHandle was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' +
467
+ 'Instead, add a ref directly to the element you want to reference.' +
468
+ '\n' +
469
+ '\n in RCTView (at **)' +
470
+ '\n in StrictMode (at **)' +
471
+ '\n in ContainsStrictModeChild (at **)' +
472
+ '\n' +
473
+ '\nLearn more about using refs safely here:' +
474
+ '\nhttps://fb.me/react-strict-mode-find-node',
475
+ ]);
476
+ expect(match).toBe(child._nativeTag);
477
+ });
478
+
479
+ it('findNodeHandle should warn if passed a component that is inside StrictMode', () => {
480
+ const View = createReactNativeComponentClass('RCTView', () => ({
481
+ validAttributes: {foo: true},
482
+ uiViewClassName: 'RCTView',
483
+ }));
484
+
485
+ let parent = undefined;
486
+ let child = undefined;
487
+
488
+ class IsInStrictMode extends React.Component {
489
+ render() {
490
+ return <View ref={n => (child = n)} />;
491
+ }
492
+ }
493
+
494
+ ReactFabric.render(
495
+ <StrictMode>
496
+ <IsInStrictMode ref={n => (parent = n)} />
497
+ </StrictMode>,
498
+ 11,
499
+ );
500
+
501
+ let match;
502
+ expect(() => (match = ReactFabric.findNodeHandle(parent))).toWarnDev([
503
+ 'Warning: findNodeHandle is deprecated in StrictMode. ' +
504
+ 'findNodeHandle was passed an instance of IsInStrictMode which is inside StrictMode. ' +
505
+ 'Instead, add a ref directly to the element you want to reference.' +
506
+ '\n' +
507
+ '\n in RCTView (at **)' +
508
+ '\n in IsInStrictMode (at **)' +
509
+ '\n in StrictMode (at **)' +
510
+ '\n' +
511
+ '\nLearn more about using refs safely here:' +
512
+ '\nhttps://fb.me/react-strict-mode-find-node',
513
+ ]);
514
+ expect(match).toBe(child._nativeTag);
515
+ });
516
});
packages/react-native-renderer/src/__tests__/ReactNativeMount-test.internal.js
+77
@@ -11,6 +11,7 @@
11
'use strict';
12
13
let React;
14
+let StrictMode;
15
let ReactNative;
16
let createReactNativeComponentClass;
17
let UIManager;
@@ -20,6 +21,7 @@ describe('ReactNative', () => {
21
jest.resetModules();
22
23
React = require('react');
24
+ StrictMode = React.StrictMode;
25
ReactNative = require('react-native-renderer');
26
UIManager = require('UIManager');
27
createReactNativeComponentClass = require('ReactNativeViewConfigRegistry')
@@ -258,4 +260,79 @@ describe('ReactNative', () => {
260
11,
261
);
262
});
263
+
264
+ it('findNodeHandle should warn if used to find a host component inside StrictMode', () => {
265
+ const View = createReactNativeComponentClass('RCTView', () => ({
266
+ validAttributes: {foo: true},
267
+ uiViewClassName: 'RCTView',
268
+ }));
269
+
270
+ let parent = undefined;
271
+ let child = undefined;
272
+
273
+ class ContainsStrictModeChild extends React.Component {
274
+ render() {
275
+ return (
276
+ <StrictMode>
277
+ <View ref={n => (child = n)} />
278
+ </StrictMode>
279
+ );
280
+ }
281
+ }
282
+
283
+ ReactNative.render(<ContainsStrictModeChild ref={n => (parent = n)} />, 11);
284
+
285
+ let match;
286
+ expect(() => (match = ReactNative.findNodeHandle(parent))).toWarnDev([
287
+ 'Warning: findNodeHandle is deprecated in StrictMode. ' +
288
+ 'findNodeHandle was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' +
289
+ 'Instead, add a ref directly to the element you want to reference.' +
290
+ '\n' +
291
+ '\n in RCTView (at **)' +
292
+ '\n in StrictMode (at **)' +
293
+ '\n in ContainsStrictModeChild (at **)' +
294
+ '\n' +
295
+ '\nLearn more about using refs safely here:' +
296
+ '\nhttps://fb.me/react-strict-mode-find-node',
297
+ ]);
298
+ expect(match).toBe(child._nativeTag);
299
+ });
300
+
301
+ it('findNodeHandle should warn if passed a component that is inside StrictMode', () => {
302
+ const View = createReactNativeComponentClass('RCTView', () => ({
303
+ validAttributes: {foo: true},
304
+ uiViewClassName: 'RCTView',
305
+ }));
306
+
307
+ let parent = undefined;
308
+ let child = undefined;
309
+
310
+ class IsInStrictMode extends React.Component {
311
+ render() {
312
+ return <View ref={n => (child = n)} />;
313
+ }
314
+ }
315
+
316
+ ReactNative.render(
317
+ <StrictMode>
318
+ <IsInStrictMode ref={n => (parent = n)} />
319
+ </StrictMode>,
320
+ 11,
321
+ );
322
+
323
+ let match;
324
+ expect(() => (match = ReactNative.findNodeHandle(parent))).toWarnDev([
325
+ 'Warning: findNodeHandle is deprecated in StrictMode. ' +
326
+ 'findNodeHandle was passed an instance of IsInStrictMode which is inside StrictMode. ' +
327
+ 'Instead, add a ref directly to the element you want to reference.' +
328
+ '\n' +
329
+ '\n in RCTView (at **)' +
330
+ '\n in IsInStrictMode (at **)' +
331
+ '\n in StrictMode (at **)' +
332
+ '\n' +
333
+ '\nLearn more about using refs safely here:' +
334
+ '\nhttps://fb.me/react-strict-mode-find-node',
335
+ ]);
336
+ expect(match).toBe(child._nativeTag);
337
+ });
338
});
packages/react-noop-renderer/src/createReactNoop.js
+6
@@ -522,6 +522,12 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
522
if (typeof component.id === 'number') {
523
return component;
524
}
525
+ if (__DEV__) {
526
+ return NoopRenderer.findHostInstanceWithWarning(
527
+ component,
528
+ 'findInstance',
529
+ );
530
+ }
531
return NoopRenderer.findHostInstance(component);
532
},
533
packages/react-reconciler/src/ReactFiberReconciler.js
+67
@@ -61,6 +61,8 @@ import {
61
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
62
import ReactFiberInstrumentation from './ReactFiberInstrumentation';
63
import * as ReactCurrentFiber from './ReactCurrentFiber';
64
+import {getStackByFiberInDevAndProd} from './ReactCurrentFiber';
65
+import {StrictMode} from './ReactTypeOfMode';
66
67
type OpaqueRoot = FiberRoot;
68
@@ -82,9 +84,11 @@ type DevToolsConfig = {|
84
|};
85
86
let didWarnAboutNestedUpdates;
87
+let didWarnAboutFindNodeInStrictMode;
88
89
if (__DEV__) {
90
didWarnAboutNestedUpdates = false;
91
+ didWarnAboutFindNodeInStrictMode = {};
92
}
93
94
function getContextForSubtree(
@@ -209,6 +213,67 @@ function findHostInstance(component: Object): PublicInstance | null {
213
return hostFiber.stateNode;
214
}
215
216
+function findHostInstanceWithWarning(
217
+ component: Object,
218
+ methodName: string,
219
+): PublicInstance | null {
220
+ if (__DEV__) {
221
+ const fiber = ReactInstanceMap.get(component);
222
+ if (fiber === undefined) {
223
+ if (typeof component.render === 'function') {
224
+ invariant(false, 'Unable to find node on an unmounted component.');
225
+ } else {
226
+ invariant(
227
+ false,
228
+ 'Argument appears to not be a ReactComponent. Keys: %s',
229
+ Object.keys(component),
230
+ );
231
+ }
232
+ }
233
+ const hostFiber = findCurrentHostFiber(fiber);
234
+ if (hostFiber === null) {
235
+ return null;
236
+ }
237
+ if (hostFiber.mode & StrictMode) {
238
+ const componentName = getComponentName(fiber.type) || 'Component';
239
+ if (!didWarnAboutFindNodeInStrictMode[componentName]) {
240
+ didWarnAboutFindNodeInStrictMode[componentName] = true;
241
+ if (fiber.mode & StrictMode) {
242
+ warningWithoutStack(
243
+ false,
244
+ '%s is deprecated in StrictMode. ' +
245
+ '%s was passed an instance of %s which is inside StrictMode. ' +
246
+ 'Instead, add a ref directly to the element you want to reference.' +
247
+ '\n%s' +
248
+ '\n\nLearn more about using refs safely here:' +
249
+ '\nhttps://fb.me/react-strict-mode-find-node',
250
+ methodName,
251
+ methodName,
252
+ componentName,
253
+ getStackByFiberInDevAndProd(hostFiber),
254
+ );
255
+ } else {
256
+ warningWithoutStack(
257
+ false,
258
+ '%s is deprecated in StrictMode. ' +
259
+ '%s was passed an instance of %s which renders StrictMode children. ' +
260
+ 'Instead, add a ref directly to the element you want to reference.' +
261
+ '\n%s' +
262
+ '\n\nLearn more about using refs safely here:' +
263
+ '\nhttps://fb.me/react-strict-mode-find-node',
264
+ methodName,
265
+ methodName,
266
+ componentName,
267
+ getStackByFiberInDevAndProd(hostFiber),
268
+ );
269
+ }
270
+ }
271
+ }
272
+ return hostFiber.stateNode;
273
+ }
274
+ return findHostInstance(component);
275
+}
276
+
277
export function createContainer(
278
containerInfo: Container,
279
isConcurrent: boolean,
@@ -266,6 +331,8 @@ export function getPublicRootInstance(
331
332
export {findHostInstance};
333
334
+export {findHostInstanceWithWarning};
335
+
336
export function findHostInstanceWithNoPortals(
337
fiber: Fiber,
338
): PublicInstance | null {
packages/react-reconciler/src/__tests__/ReactIncrementalReflection-test.internal.js
+20
-7
@@ -138,22 +138,35 @@ describe('ReactIncrementalReflection', () => {
138
139
let classInstance = null;
140
141
+ function findInstance(inst) {
142
+ // We ignore warnings fired by findInstance because we are testing
143
+ // that the actual behavior still works as expected even though it
144
+ // is deprecated.
145
+ let oldConsoleError = console.error;
146
+ console.error = jest.fn();
147
+ try {
148
+ return ReactNoop.findInstance(inst);
149
+ } finally {
150
+ console.error = oldConsoleError;
151
+ }
152
+ }
153
+
154
class Component extends React.Component {
155
UNSAFE_componentWillMount() {
156
classInstance = this;
144
- ops.push('componentWillMount', ReactNoop.findInstance(this));
157
+ ops.push('componentWillMount', findInstance(this));
158
}
159
componentDidMount() {
147
- ops.push('componentDidMount', ReactNoop.findInstance(this));
160
+ ops.push('componentDidMount', findInstance(this));
161
}
162
UNSAFE_componentWillUpdate() {
150
- ops.push('componentWillUpdate', ReactNoop.findInstance(this));
163
+ ops.push('componentWillUpdate', findInstance(this));
164
}
165
componentDidUpdate() {
153
- ops.push('componentDidUpdate', ReactNoop.findInstance(this));
166
+ ops.push('componentDidUpdate', findInstance(this));
167
}
168
componentWillUnmount() {
156
- ops.push('componentWillUnmount', ReactNoop.findInstance(this));
169
+ ops.push('componentWillUnmount', findInstance(this));
170
}
171
render() {
172
ops.push('render');
@@ -193,7 +206,7 @@ describe('ReactIncrementalReflection', () => {
206
expect(classInstance).toBeDefined();
207
// The instance has been complete but is still not committed so it should
208
// not find any host nodes in it.
196
- expect(ReactNoop.findInstance(classInstance)).toBe(null);
209
+ expect(findInstance(classInstance)).toBe(null);
210
211
expect(ReactNoop.flush).toWarnDev(
212
'componentWillMount: Please update the following components ' +
@@ -206,7 +219,7 @@ describe('ReactIncrementalReflection', () => {
219
const hostSpan = classInstance.span;
220
expect(hostSpan).toBeDefined();
221
209
- expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
222
+ expect(findInstance(classInstance)).toBe(hostSpan);
223
224
expect(ops).toEqual(['componentDidMount', hostSpan]);
225