Warn if getDOMNode or isMounted is accessed in render
This is an anti-pattern that can be easily avoided by putting the logic in componentDidMount and componentDidUpdate instead. It creates a dependency on stale data inside render without enforcing a two-pass render.
Sebastian Markbage committed
Feb 17, 2015 at 11:49 UTC
2490289c4adda494cc774d6a30c90de0db41db6f
6 files changed
+81
-27
src/browser/__tests__/ReactDOM-test.js
+1
@@ -118,4 +118,5 @@ describe('ReactDOM', function() {
118
expect(element.type).toBe('div');
119
expect(console.warn.argsForCall.length).toBe(0);
120
});
121
+
122
});
src/browser/findDOMNode.js
+18
@@ -11,11 +11,14 @@
11
*/
12
13
'use strict';
14
+
15
+var ReactCurrentOwner = require('ReactCurrentOwner');
16
var ReactInstanceMap = require('ReactInstanceMap');
17
var ReactMount = require('ReactMount');
18
19
var invariant = require('invariant');
20
var isNode = require('isNode');
21
+var warning = require('warning');
22
23
/**
24
* Returns the DOM node rendered by this element.
@@ -24,6 +27,21 @@ var isNode = require('isNode');
27
* @return {DOMElement} The root node of this element.
28
*/
29
function findDOMNode(componentOrElement) {
30
+ if (__DEV__) {
31
+ var owner = ReactCurrentOwner.current;
32
+ if (owner !== null) {
33
+ warning(
34
+ owner._warnedAboutRefsInRender,
35
+ '%s is accessing getDOMNode or findDOMNode inside its render(). ' +
36
+ 'render() should be a pure function of props and state. It should ' +
37
+ 'never access something that requires stale data from the previous ' +
38
+ 'render, such as refs. Move this logic to componentDidMount and ' +
39
+ 'componentDidUpdate instead.',
40
+ owner.getName() || 'A component'
41
+ );
42
+ owner._warnedAboutRefsInRender = true;
43
+ }
44
+ }
45
if (componentOrElement == null) {
46
return null;
47
}
src/classic/class/ReactClass.js
+16
@@ -12,6 +12,7 @@
12
'use strict';
13
14
var ReactComponent = require('ReactComponent');
15
+var ReactCurrentOwner = require('ReactCurrentOwner');
16
var ReactElement = require('ReactElement');
17
var ReactErrorUtils = require('ReactErrorUtils');
18
var ReactInstanceMap = require('ReactInstanceMap');
@@ -746,6 +747,21 @@ var ReactClassMixin = {
747
* @final
748
*/
749
isMounted: function() {
750
+ if (__DEV__) {
751
+ var owner = ReactCurrentOwner.current;
752
+ if (owner !== null) {
753
+ warning(
754
+ owner._warnedAboutRefsInRender,
755
+ '%s is accessing isMounted inside its render() function. ' +
756
+ 'render() should be a pure function of props and state. It should ' +
757
+ 'never access something that requires stale data from the previous ' +
758
+ 'render, such as refs. Move this logic to componentDidMount and ' +
759
+ 'componentDidUpdate instead.',
760
+ owner.getName() || 'A component'
761
+ );
762
+ owner._warnedAboutRefsInRender = true;
763
+ }
764
+ }
765
var internalInstance = ReactInstanceMap.get(this);
766
return (
767
internalInstance &&
src/core/__tests__/ReactComponent-test.js
-20
@@ -254,26 +254,6 @@ describe('ReactComponent', function() {
254
]);
255
});
256
257
- it('should correctly determine if a component is mounted', function() {
258
- var Component = React.createClass({
259
- componentWillMount: function() {
260
- expect(this.isMounted()).toBeFalsy();
261
- },
262
- componentDidMount: function() {
263
- expect(this.isMounted()).toBeTruthy();
264
- },
265
- render: function() {
266
- expect(this.isMounted()).toBeFalsy()
267
- return <div/>;
268
- }
269
- });
270
-
271
- var element = <Component />;
272
-
273
- var instance = ReactTestUtils.renderIntoDocument(element);
274
- expect(instance.isMounted()).toBeTruthy();
275
- });
276
-
257
it('fires the callback after a component is rendered', function() {
258
var callback = mocks.getMockFunction();
259
var container = document.createElement('div');
src/core/__tests__/ReactComponentLifeCycle-test.js
+45
-7
@@ -261,16 +261,54 @@ describe('ReactComponentLifeCycle', function() {
261
);
262
});
263
264
- it('is not mounted inside initial render', function() {
265
- var InitialRender = React.createClass({
264
+ it('should correctly determine if a component is mounted', function() {
265
+ spyOn(console, 'warn');
266
+ var Component = React.createClass({
267
+ componentWillMount: function() {
268
+ expect(this.isMounted()).toBeFalsy();
269
+ },
270
+ componentDidMount: function() {
271
+ expect(this.isMounted()).toBeTruthy();
272
+ },
273
render: function() {
267
- expect(this.isMounted()).toBe(false);
268
- return (
269
- <div></div>
270
- );
274
+ expect(this.isMounted()).toBeFalsy()
275
+ return <div/>;
276
}
277
});
273
- ReactTestUtils.renderIntoDocument(<InitialRender />);
278
+
279
+ var element = <Component />;
280
+
281
+ var instance = ReactTestUtils.renderIntoDocument(element);
282
+ expect(instance.isMounted()).toBeTruthy();
283
+
284
+ expect(console.warn.argsForCall.length).toBe(1);
285
+ expect(console.warn.argsForCall[0][0]).toContain(
286
+ 'Component is accessing isMounted inside its render()'
287
+ );
288
+ });
289
+
290
+ it('warns if getDOMNode is used inside render', function() {
291
+ spyOn(console, 'warn');
292
+ var Component = React.createClass({
293
+ getInitialState: function() {
294
+ return { isMounted: false };
295
+ },
296
+ componentDidMount: function() {
297
+ this.setState({ isMounted: true });
298
+ },
299
+ render: function() {
300
+ if (this.state.isMounted) {
301
+ expect(this.getDOMNode().tagName).toBe('DIV');
302
+ }
303
+ return <div/>;
304
+ }
305
+ });
306
+
307
+ ReactTestUtils.renderIntoDocument(<Component />);
308
+ expect(console.warn.argsForCall.length).toBe(1);
309
+ expect(console.warn.argsForCall[0][0]).toContain(
310
+ 'Component is accessing getDOMNode or findDOMNode inside its render()'
311
+ );
312
});
313
314
it('should carry through each of the phases of setup', function() {
src/core/instantiateReactComponent.js
+1
@@ -116,6 +116,7 @@ function instantiateReactComponent(node, parentCompositeType) {
116
117
if (__DEV__) {
118
instance._isOwnerNecessary = false;
119
+ instance._warnedAboutRefsInRender = false;
120
}
121
122
// Internal instances should fully constructed at this point, so they should