@samitouri / QOS-React / commits / 4773fdf7cd

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