Repository: yannickcr/eslint-plugin-react
Pull request #197 (2015-08-25 05:37:59)

Add no-set-state rule
As explained in the updated docs in this commit: "When using an architecture that separates your application state from your UI components (e.g. Flux), it may be desirable to forbid the use of local component state."

It's highly likely that this rule might need some changes, particularly since I haven't worked with ESLint internals before, but I wanted to get my first attempt in a pull request to at least get the conversation started.

Is this a rule that you'd consider including?


Patch:
diff --git a/README.md b/README.md
index b007c114..9bcf8974 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,7 @@ Finally, enable all of the rules that you would like to use.
     "react/jsx-uses-react": 1,
     "react/jsx-uses-vars": 1,
     "react/no-danger": 1,
+    "react/no-set-state": 1,
     "react/no-did-mount-set-state": 1,
     "react/no-did-update-set-state": 1,
     "react/no-multi-comp": 1,
@@ -85,6 +86,7 @@ Finally, enable all of the rules that you would like to use.
 * [jsx-uses-react](docs/rules/jsx-uses-react.md): Prevent React to be incorrectly marked as unused
 * [jsx-uses-vars](docs/rules/jsx-uses-vars.md): Prevent variables used in JSX to be incorrectly marked as unused
 * [no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
+* [no-set-state](docs/rules/no-set-state.md): Prevent usage of setState
 * [no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of setState in componentDidMount
 * [no-did-update-set-state](docs/rules/no-did-update-set-state.md): Prevent usage of setState in componentDidUpdate
 * [no-multi-comp](docs/rules/no-multi-comp.md): Prevent multiple component definition per file
diff --git a/docs/rules/no-set-state.md b/docs/rules/no-set-state.md
new file mode 100644
index 00000000..274dca03
--- /dev/null
+++ b/docs/rules/no-set-state.md
@@ -0,0 +1,35 @@
+# Prevent usage of setState (no-set-state)
+
+When using an architecture that separates your application state from your UI components (e.g. Flux), it may be desirable to forbid the use of local component state.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```js
+var Hello = React.createClass({
+  getInitialState: function() {
+    return {
+      name: this.props.name
+    };
+  },
+  handleClick: function() {
+    this.setState({
+      name: this.props.name.toUpperCase()
+    });
+  },
+  render: function() {
+    return <div onClick={this.handleClick.bind(this)}>Hello {this.state.name}</div>;
+  }
+});
+```
+
+The following patterns are not considered warnings:
+
+```js
+var Hello = React.createClass({
+  render: function() {
+    return <div onClick={this.props.handleClick}>Hello {this.props.name}</div>;
+  }
+});
+```
diff --git a/index.js b/index.js
index 225ed9aa..6ee3ecfc 100644
--- a/index.js
+++ b/index.js
@@ -9,6 +9,7 @@ module.exports = {
     'wrap-multilines': require('./lib/rules/wrap-multilines'),
     'self-closing-comp': require('./lib/rules/self-closing-comp'),
     'no-danger': require('./lib/rules/no-danger'),
+    'no-set-state': require('./lib/rules/no-set-state'),
     'no-did-mount-set-state': require('./lib/rules/no-did-mount-set-state'),
     'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
     'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
@@ -35,6 +36,7 @@ module.exports = {
     'wrap-multilines': 0,
     'self-closing-comp': 0,
     'no-danger': 0,
+    'no-set-state': 0,
     'no-did-mount-set-state': 0,
     'no-did-update-set-state': 0,
     'react-in-jsx-scope': 0,
diff --git a/lib/rules/no-set-state.js b/lib/rules/no-set-state.js
new file mode 100644
index 00000000..94f2217c
--- /dev/null
+++ b/lib/rules/no-set-state.js
@@ -0,0 +1,39 @@
+/**
+ * @fileoverview Prevent usage of setState
+ * @author Mark Dalgleish
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+  // --------------------------------------------------------------------------
+  // Public
+  // --------------------------------------------------------------------------
+
+  return {
+
+    CallExpression: function(node) {
+      var callee = node.callee;
+      if (callee.type !== 'MemberExpression') {
+        return;
+      }
+      if (callee.object.type !== 'ThisExpression' || callee.property.name !== 'setState') {
+        return;
+      }
+      var ancestors = context.getAncestors(callee);
+      for (var i = 0, j = ancestors.length; i < j; i++) {
+        if (ancestors[i].type === 'Property' || ancestors[i].type === 'MethodDefinition') {
+          context.report(callee, 'Do not use setState');
+          break;
+        }
+      }
+    }
+  };
+
+};
+
+module.exports.schema = [];


Test patch:
diff --git a/tests/lib/rules/no-set-state.js b/tests/lib/rules/no-set-state.js
new file mode 100644
index 00000000..306ed31d
--- /dev/null
+++ b/tests/lib/rules/no-set-state.js
@@ -0,0 +1,115 @@
+/**
+ * @fileoverview Prevent usage of setState
+ * @author Mark Dalgleish
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/no-set-state');
+var RuleTester = require('eslint').RuleTester;
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('no-set-state', rule, {
+
+  valid: [{
+    code: [
+      'var Hello = function() {',
+      '  this.setState({})',
+      '};'
+    ].join('\n'),
+    ecmaFeatures: {}
+  }, {
+    code: [
+      'var Hello = React.createClass({',
+      '  render: function() {',
+      '    return <div>Hello {this.props.name}</div>;',
+      '  }',
+      '});'
+    ].join('\n'),
+    ecmaFeatures: {
+      jsx: true
+    }
+  }, {
+    code: [
+      'var Hello = React.createClass({',
+      '  componentDidUpdate: function() {',
+      '    someNonMemberFunction(arg);',
+      '    this.someHandler = this.setState;',
+      '  },',
+      '  render: function() {',
+      '    return <div>Hello {this.props.name}</div>;',
+      '  }',
+      '});'
+    ].join('\n'),
+    ecmaFeatures: {
+      jsx: true
+    }
+  }],
+
+  invalid: [{
+    code: [
+      'var Hello = React.createClass({',
+      '  componentDidUpdate: function() {',
+      '    this.setState({',
+      '      name: this.props.name.toUpperCase()',
+      '    });',
+      '  },',
+      '  render: function() {',
+      '    return <div>Hello {this.state.name}</div>;',
+      '  }',
+      '});'
+    ].join('\n'),
+    ecmaFeatures: {
+      jsx: true
+    },
+    errors: [{
+      message: 'Do not use setState'
+    }]
+  }, {
+    code: [
+      'var Hello = React.createClass({',
+      '  someMethod: function() {',
+      '    this.setState({',
+      '      name: this.props.name.toUpperCase()',
+      '    });',
+      '  },',
+      '  render: function() {',
+      '    return <div onClick={this.someMethod.bind(this)}>Hello {this.state.name}</div>;',
+      '  }',
+      '});'
+    ].join('\n'),
+    ecmaFeatures: {
+      jsx: true
+    },
+    errors: [{
+      message: 'Do not use setState'
+    }]
+  }, {
+    code: [
+      'class Hello extends React.Component {',
+      '  someMethod() {',
+      '    this.setState({',
+      '      name: this.props.name.toUpperCase()',
+      '    });',
+      '  }',
+      '  render() {',
+      '    return <div onClick={this.someMethod.bind(this)}>Hello {this.state.name}</div>;',
+      '  }',
+      '};'
+    ].join('\n'),
+    ecmaFeatures: {
+      jsx: true,
+      classes: true
+    },
+    errors: [{
+      message: 'Do not use setState'
+    }]
+  }]
+});