diff --git a/docs/rules/no-did-mount-set-state.md b/docs/rules/no-did-mount-set-state.md index e6c9c1d0b8..91a8762f77 100644 --- a/docs/rules/no-did-mount-set-state.md +++ b/docs/rules/no-did-mount-set-state.md @@ -4,15 +4,32 @@ Updating the state after a component mount will trigger a second `render()` call ## Rule Details +This rule is aimed to forbids the use of `this.setState` in `componentDidMount`. + The following patterns are considered warnings: ```js var Hello = React.createClass({ componentDidMount: function() { - this.setState({ - name: this.props.name.toUpperCase() + this.setState({ + name: this.props.name.toUpperCase() + }); + }, + render: function() { + return
Hello {this.state.name}
; + } +}); +``` + +```js +var Hello = React.createClass({ + componentDidMount: function() { + this.onMount(function callback(newName) { + this.setState({ + name: newName }); - }, + }); + }, render: function() { return
Hello {this.state.name}
; } @@ -31,3 +48,47 @@ var Hello = React.createClass({ } }); ``` + +## Rule Options + +```js +... +"no-did-mount-set-state": [, ] +... +``` + +### `allow-in-func` mode + +By default this rule forbids any call to `this.setState` in `componentDidMount`. But since `componentDidMount` is a common place to set some event listeners, you may end up with calls to `this.setState` in some callbacks. The `allow-in-func` mode allows you to use `this.setState` in `componentDidMount` as long as they are called within a function. + +The following patterns are considered warnings: + +```js +var Hello = React.createClass({ + componentDidMount: function() { + this.setState({ + name: this.props.name.toUpperCase() + }); + }, + render: function() { + return
Hello {this.state.name}
; + } +}); +``` + +The following patterns are not considered warnings: + +```js +var Hello = React.createClass({ + componentDidMount: function() { + this.onMount(function callback(newName) { + this.setState({ + name: newName + }); + }); + }, + render: function() { + return
Hello {this.state.name}
; + } +}); +``` diff --git a/lib/rules/no-did-mount-set-state.js b/lib/rules/no-did-mount-set-state.js index dae3d94f32..f251195abc 100644 --- a/lib/rules/no-did-mount-set-state.js +++ b/lib/rules/no-did-mount-set-state.js @@ -10,6 +10,8 @@ module.exports = function(context) { + var mode = context.options[0] || 'never'; + // -------------------------------------------------------------------------- // Public // -------------------------------------------------------------------------- @@ -18,18 +20,28 @@ module.exports = function(context) { CallExpression: function(node) { var callee = node.callee; - if (callee.type !== 'MemberExpression') { - return; - } - if (callee.object.type !== 'ThisExpression' || callee.property.name !== 'setState') { + if ( + callee.type !== 'MemberExpression' || + callee.object.type !== 'ThisExpression' || + callee.property.name !== 'setState' + ) { return; } - var ancestors = context.getAncestors(callee); + var ancestors = context.getAncestors(callee).reverse(); + var depth = 0; for (var i = 0, j = ancestors.length; i < j; i++) { - if (ancestors[i].type !== 'Property' || ancestors[i].key.name !== 'componentDidMount') { + if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) { + depth++; + } + if ( + ancestors[i].type !== 'Property' || + ancestors[i].key.name !== 'componentDidMount' || + (mode === 'allow-in-func' && depth > 1) + ) { continue; } context.report(callee, 'Do not use setState in componentDidMount'); + break; } } }; diff --git a/tests/lib/rules/no-did-mount-set-state.js b/tests/lib/rules/no-did-mount-set-state.js index 3d729ff321..59c840bdf6 100644 --- a/tests/lib/rules/no-did-mount-set-state.js +++ b/tests/lib/rules/no-did-mount-set-state.js @@ -11,6 +11,8 @@ var eslint = require('eslint').linter; var ESLintTester = require('eslint-tester'); +require('babel-eslint'); + // ------------------------------------------------------------------------------ // Tests // ------------------------------------------------------------------------------ @@ -32,10 +34,7 @@ eslintTester.addRuleTest('lib/rules/no-did-mount-set-state', { }, { code: [ 'var Hello = React.createClass({', - 'componentDidMount: function() {},', - ' render: function() {', - ' return
Hello {this.props.name}
;', - ' }', + ' componentDidMount: function() {}', '});' ].join('\n'), ecmaFeatures: { @@ -47,30 +46,126 @@ eslintTester.addRuleTest('lib/rules/no-did-mount-set-state', { ' componentDidMount: function() {', ' someNonMemberFunction(arg);', ' this.someHandler = this.setState;', - ' },', - ' render: function() {', - ' return
Hello {this.props.name}
;', ' }', '});' ].join('\n'), ecmaFeatures: { jsx: true } + }, { + code: [ + 'var Hello = React.createClass({', + ' componentDidMount: function() {', + ' someClass.onSomeEvent(function(data) {', + ' this.setState({', + ' data: data', + ' });', + ' })', + ' }', + '});' + ].join('\n'), + args: [1, 'allow-in-func'], + ecmaFeatures: { + jsx: true + } + }, { + code: [ + 'var Hello = React.createClass({', + ' componentDidMount: function() {', + ' function handleEvent(data) {', + ' this.setState({', + ' data: data', + ' });', + ' }', + ' someClass.onSomeEvent(handleEvent)', + ' }', + '});' + ].join('\n'), + parser: 'babel-eslint', + args: [1, 'allow-in-func'], + ecmaFeatures: { + jsx: true + } }], invalid: [{ code: [ 'var Hello = React.createClass({', ' componentDidMount: function() {', - ' this.setState({', - ' name: this.props.name.toUpperCase()', - ' });', - ' },', - ' render: function() {', - ' return
Hello {this.state.name}
;', + ' this.setState({', + ' data: data', + ' });', + ' }', + '});' + ].join('\n'), + ecmaFeatures: { + jsx: true + }, + errors: [{ + message: 'Do not use setState in componentDidMount' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' componentDidMount: function() {', + ' this.setState({', + ' data: data', + ' });', + ' }', + '});' + ].join('\n'), + args: [1, 'allow-in-func'], + ecmaFeatures: { + jsx: true + }, + errors: [{ + message: 'Do not use setState in componentDidMount' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' componentDidMount: function() {', + ' someClass.onSomeEvent(function(data) {', + ' this.setState({', + ' data: data', + ' });', + ' })', + ' }', + '});' + ].join('\n'), + ecmaFeatures: { + jsx: true + }, + errors: [{ + message: 'Do not use setState in componentDidMount' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' componentDidMount: function() {', + ' if (true) {', + ' this.setState({', + ' data: data', + ' });', + ' }', + ' }', + '});' + ].join('\n'), + ecmaFeatures: { + jsx: true + }, + errors: [{ + message: 'Do not use setState in componentDidMount' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' componentDidMount: function() {', + ' someClass.onSomeEvent((data) => this.setState({data: data}));', ' }', '});' ].join('\n'), + parser: 'babel-eslint', ecmaFeatures: { jsx: true },