@samitouri / QOS-React-2 / commits / 2490289c4a

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