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
},