Skip to content

Commit 2fe2ac5

Browse files
committed
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.
1 parent 0af8199 commit 2fe2ac5

File tree

7 files changed

+191
-11
lines changed

7 files changed

+191
-11
lines changed

packages/react-dom/src/__tests__/findDOMNode-test.js

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
const React = require('react');
1313
const ReactDOM = require('react-dom');
1414
const ReactTestUtils = require('react-dom/test-utils');
15+
const StrictMode = React.StrictMode;
1516

1617
describe('findDOMNode', () => {
1718
it('findDOMNode should return null if passed null', () => {
@@ -94,7 +95,72 @@ describe('findDOMNode', () => {
9495
return <div />;
9596
}
9697
}
97-
9898
expect(() => ReactTestUtils.renderIntoDocument(<Bar />)).not.toThrow();
9999
});
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 a StrictMode subtree. ' +
123+
'The nearest child is in StrictMode. ' +
124+
'Use an explicit ref directly on the element you want to get a handle on.' +
125+
'\n' +
126+
'\n in div (at **)' +
127+
'\n in StrictMode (at **)' +
128+
'\n in ContainsStrictModeChild (at **)' +
129+
'\n' +
130+
'\nLearn more about using refs safely here:' +
131+
'\nhttps://fb.me/react-strict-mode-find-node',
132+
]);
133+
expect(match).toBe(child);
134+
});
135+
136+
it('findDOMNode should warn if passed a component that is inside StrictMode', () => {
137+
let parent = undefined;
138+
let child = undefined;
139+
140+
class IsInStrictMode extends React.Component {
141+
render() {
142+
return <div ref={n => (child = n)} />;
143+
}
144+
}
145+
146+
ReactTestUtils.renderIntoDocument(
147+
<StrictMode>
148+
<IsInStrictMode ref={n => (parent = n)} />
149+
</StrictMode>,
150+
);
151+
152+
let match;
153+
expect(() => (match = ReactDOM.findDOMNode(parent))).toWarnDev([
154+
'Warning: findDOMNode is deprecated in StrictMode. ' +
155+
'findDOMNode was passed an instance of IsInStrictMode which is in a StrictMode subtree. ' +
156+
'Use an explicit ref directly on the element you want to get a handle on.' +
157+
'\n' +
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+
});
100166
});

packages/react-dom/src/client/ReactDOM.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,12 @@ const ReactDOM: Object = {
614614
if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
615615
return (componentOrElement: any);
616616
}
617-
617+
if (__DEV__) {
618+
return DOMRenderer.findHostInstanceWithWarning(
619+
componentOrElement,
620+
'findDOMNode',
621+
);
622+
}
618623
return DOMRenderer.findHostInstance(componentOrElement);
619624
},
620625

packages/react-native-renderer/src/ReactFabric.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import warningWithoutStack from 'shared/warningWithoutStack';
2929

3030
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
3131
const findHostInstance = ReactFabricRenderer.findHostInstance;
32+
const findHostInstanceWithWarning =
33+
ReactFabricRenderer.findHostInstanceWithWarning;
3234

3335
function findNodeHandle(componentOrHandle: any): ?number {
3436
if (__DEV__) {
@@ -60,7 +62,16 @@ function findNodeHandle(componentOrHandle: any): ?number {
6062
if (componentOrHandle.canonical && componentOrHandle.canonical._nativeTag) {
6163
return componentOrHandle.canonical._nativeTag;
6264
}
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+
6475
if (hostInstance == null) {
6576
return hostInstance;
6677
}

packages/react-native-renderer/src/ReactNativeRenderer.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import warningWithoutStack from 'shared/warningWithoutStack';
3232

3333
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
3434
const findHostInstance = ReactNativeFiberRenderer.findHostInstance;
35+
const findHostInstanceWithWarning =
36+
ReactNativeFiberRenderer.findHostInstanceWithWarning;
3537

3638
function findNodeHandle(componentOrHandle: any): ?number {
3739
if (__DEV__) {
@@ -63,7 +65,16 @@ function findNodeHandle(componentOrHandle: any): ?number {
6365
if (componentOrHandle.canonical && componentOrHandle.canonical._nativeTag) {
6466
return componentOrHandle.canonical._nativeTag;
6567
}
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+
6778
if (hostInstance == null) {
6879
return hostInstance;
6980
}

packages/react-noop-renderer/src/createReactNoop.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,12 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
522522
if (typeof component.id === 'number') {
523523
return component;
524524
}
525+
if (__DEV__) {
526+
return NoopRenderer.findHostInstanceWithWarning(
527+
component,
528+
'findInstance',
529+
);
530+
}
525531
return NoopRenderer.findHostInstance(component);
526532
},
527533

packages/react-reconciler/src/ReactFiberReconciler.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ import {
6161
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
6262
import ReactFiberInstrumentation from './ReactFiberInstrumentation';
6363
import * as ReactCurrentFiber from './ReactCurrentFiber';
64+
import {getStackByFiberInDevAndProd} from './ReactCurrentFiber';
65+
import {StrictMode} from './ReactTypeOfMode';
6466

6567
type OpaqueRoot = FiberRoot;
6668

@@ -82,9 +84,11 @@ type DevToolsConfig = {|
8284
|};
8385

8486
let didWarnAboutNestedUpdates;
87+
let didWarnAboutFindNodeInStrictMode;
8588

8689
if (__DEV__) {
8790
didWarnAboutNestedUpdates = false;
91+
didWarnAboutFindNodeInStrictMode = {};
8892
}
8993

9094
function getContextForSubtree(
@@ -209,6 +213,68 @@ function findHostInstance(component: Object): PublicInstance | null {
209213
return hostFiber.stateNode;
210214
}
211215

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 in a StrictMode subtree. ' +
246+
'Use an explicit ref directly on the element you want to get a handle on.' +
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(fiber),
254+
);
255+
} else {
256+
warningWithoutStack(
257+
false,
258+
'%s is deprecated in StrictMode. ' +
259+
'%s was passed an instance of %s which renders a StrictMode subtree. ' +
260+
'The nearest child is in StrictMode. ' +
261+
'Use an explicit ref directly on the element you want to get a handle on.' +
262+
'\n%s' +
263+
'\n\nLearn more about using refs safely here:' +
264+
'\nhttps://fb.me/react-strict-mode-find-node',
265+
methodName,
266+
methodName,
267+
componentName,
268+
getStackByFiberInDevAndProd(hostFiber),
269+
);
270+
}
271+
}
272+
}
273+
return hostFiber.stateNode;
274+
}
275+
return findHostInstance(component);
276+
}
277+
212278
export function createContainer(
213279
containerInfo: Container,
214280
isConcurrent: boolean,
@@ -266,6 +332,8 @@ export function getPublicRootInstance(
266332

267333
export {findHostInstance};
268334

335+
export {findHostInstanceWithWarning};
336+
269337
export function findHostInstanceWithNoPortals(
270338
fiber: Fiber,
271339
): PublicInstance | null {

packages/react-reconciler/src/__tests__/ReactIncrementalReflection-test.internal.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,22 +138,35 @@ describe('ReactIncrementalReflection', () => {
138138

139139
let classInstance = null;
140140

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+
141154
class Component extends React.Component {
142155
UNSAFE_componentWillMount() {
143156
classInstance = this;
144-
ops.push('componentWillMount', ReactNoop.findInstance(this));
157+
ops.push('componentWillMount', findInstance(this));
145158
}
146159
componentDidMount() {
147-
ops.push('componentDidMount', ReactNoop.findInstance(this));
160+
ops.push('componentDidMount', findInstance(this));
148161
}
149162
UNSAFE_componentWillUpdate() {
150-
ops.push('componentWillUpdate', ReactNoop.findInstance(this));
163+
ops.push('componentWillUpdate', findInstance(this));
151164
}
152165
componentDidUpdate() {
153-
ops.push('componentDidUpdate', ReactNoop.findInstance(this));
166+
ops.push('componentDidUpdate', findInstance(this));
154167
}
155168
componentWillUnmount() {
156-
ops.push('componentWillUnmount', ReactNoop.findInstance(this));
169+
ops.push('componentWillUnmount', findInstance(this));
157170
}
158171
render() {
159172
ops.push('render');
@@ -193,7 +206,7 @@ describe('ReactIncrementalReflection', () => {
193206
expect(classInstance).toBeDefined();
194207
// The instance has been complete but is still not committed so it should
195208
// not find any host nodes in it.
196-
expect(ReactNoop.findInstance(classInstance)).toBe(null);
209+
expect(findInstance(classInstance)).toBe(null);
197210

198211
expect(ReactNoop.flush).toWarnDev(
199212
'componentWillMount: Please update the following components ' +
@@ -206,7 +219,7 @@ describe('ReactIncrementalReflection', () => {
206219
const hostSpan = classInstance.span;
207220
expect(hostSpan).toBeDefined();
208221

209-
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
222+
expect(findInstance(classInstance)).toBe(hostSpan);
210223

211224
expect(ops).toEqual(['componentDidMount', hostSpan]);
212225

0 commit comments

Comments
 (0)