Skip to content

Commit 0ae6bb2

Browse files
authored
Merge pull request #1139 from amplitude/no-will-update-set-state
[New] Add no-will-update-set-state rule
2 parents 7111a70 + 4405523 commit 0ae6bb2

8 files changed

+413
-118
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
102102
* [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md): Prevent invalid characters from appearing in markup
103103
* [react/no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property (fixable)
104104
* [react/no-unused-prop-types](docs/rules/no-unused-prop-types.md): Prevent definitions of unused prop types
105+
* [react/no-will-update-set-state](docs/rules/no-will-update-set-state.md): Prevent usage of `setState` in `componentWillUpdate`
105106
* [react/prefer-es6-class](docs/rules/prefer-es6-class.md): Enforce ES5 or ES6 class for React Components
106107
* [react/prefer-stateless-function](docs/rules/prefer-stateless-function.md): Enforce stateless React Components to be written as a pure function
107108
* [react/prop-types](docs/rules/prop-types.md): Prevent missing props validation in a React component definition
+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Prevent usage of setState in componentWillUpdate (no-will-update-set-state)
2+
3+
Updating the state during the componentWillUpdate step can lead to indeterminate component state and is not allowed.
4+
5+
## Rule Details
6+
7+
The following patterns are considered warnings:
8+
9+
```jsx
10+
var Hello = React.createClass({
11+
componentWillUpdate: function() {
12+
this.setState({
13+
name: this.props.name.toUpperCase()
14+
});
15+
},
16+
render: function() {
17+
return <div>Hello {this.state.name}</div>;
18+
}
19+
});
20+
```
21+
22+
The following patterns are not considered warnings:
23+
24+
```jsx
25+
var Hello = React.createClass({
26+
componentWillUpdate: function() {
27+
this.props.prepareHandler();
28+
},
29+
render: function() {
30+
return <div>Hello {this.props.name}</div>;
31+
}
32+
});
33+
```
34+
35+
```jsx
36+
var Hello = React.createClass({
37+
componentWillUpdate: function() {
38+
this.prepareHandler(function callback(newName) {
39+
this.setState({
40+
name: newName
41+
});
42+
});
43+
},
44+
render: function() {
45+
return <div>Hello {this.props.name}</div>;
46+
}
47+
});
48+
```
49+
50+
## Rule Options
51+
52+
```js
53+
...
54+
"no-will-update-set-state": [<enabled>, <mode>]
55+
...
56+
```
57+
58+
### `disallow-in-func` mode
59+
60+
By default this rule forbids any call to `this.setState` in `componentWillUpdate` outside of functions. The `disallow-in-func` mode makes this rule more strict by disallowing calls to `this.setState` even within functions.
61+
62+
The following patterns are considered warnings:
63+
64+
```jsx
65+
var Hello = React.createClass({
66+
componentDidUpdate: function() {
67+
this.setState({
68+
name: this.props.name.toUpperCase()
69+
});
70+
},
71+
render: function() {
72+
return <div>Hello {this.state.name}</div>;
73+
}
74+
});
75+
```
76+
77+
```jsx
78+
var Hello = React.createClass({
79+
componentDidUpdate: function() {
80+
this.prepareHandler(function callback(newName) {
81+
this.setState({
82+
name: newName
83+
});
84+
});
85+
},
86+
render: function() {
87+
return <div>Hello {this.state.name}</div>;
88+
}
89+
});
90+
```

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var allRules = {
2020
'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
2121
'no-render-return-value': require('./lib/rules/no-render-return-value'),
2222
'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'),
23+
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),
2324
'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
2425
'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
2526
'jsx-handler-names': require('./lib/rules/jsx-handler-names'),

lib/rules/no-did-mount-set-state.js

+2-59
Original file line numberDiff line numberDiff line change
@@ -4,63 +4,6 @@
44
*/
55
'use strict';
66

7-
// ------------------------------------------------------------------------------
8-
// Rule Definition
9-
// ------------------------------------------------------------------------------
7+
var makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule');
108

11-
module.exports = {
12-
meta: {
13-
docs: {
14-
description: 'Prevent usage of setState in componentDidMount',
15-
category: 'Best Practices',
16-
recommended: false
17-
},
18-
19-
schema: [{
20-
enum: ['disallow-in-func']
21-
}]
22-
},
23-
24-
create: function(context) {
25-
26-
var mode = context.options[0] || 'allow-in-func';
27-
28-
// --------------------------------------------------------------------------
29-
// Public
30-
// --------------------------------------------------------------------------
31-
32-
return {
33-
34-
CallExpression: function(node) {
35-
var callee = node.callee;
36-
if (
37-
callee.type !== 'MemberExpression' ||
38-
callee.object.type !== 'ThisExpression' ||
39-
callee.property.name !== 'setState'
40-
) {
41-
return;
42-
}
43-
var ancestors = context.getAncestors(callee).reverse();
44-
var depth = 0;
45-
for (var i = 0, j = ancestors.length; i < j; i++) {
46-
if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) {
47-
depth++;
48-
}
49-
if (
50-
(ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') ||
51-
ancestors[i].key.name !== 'componentDidMount' ||
52-
(mode !== 'disallow-in-func' && depth > 1)
53-
) {
54-
continue;
55-
}
56-
context.report({
57-
node: callee,
58-
message: 'Do not use setState in componentDidMount'
59-
});
60-
break;
61-
}
62-
}
63-
};
64-
65-
}
66-
};
9+
module.exports = makeNoMethodSetStateRule('componentDidMount');

lib/rules/no-did-update-set-state.js

+2-59
Original file line numberDiff line numberDiff line change
@@ -4,63 +4,6 @@
44
*/
55
'use strict';
66

7-
// ------------------------------------------------------------------------------
8-
// Rule Definition
9-
// ------------------------------------------------------------------------------
7+
var makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule');
108

11-
module.exports = {
12-
meta: {
13-
docs: {
14-
description: 'Prevent usage of setState in componentDidUpdate',
15-
category: 'Best Practices',
16-
recommended: false
17-
},
18-
19-
schema: [{
20-
enum: ['disallow-in-func']
21-
}]
22-
},
23-
24-
create: function(context) {
25-
26-
var mode = context.options[0] || 'allow-in-func';
27-
28-
// --------------------------------------------------------------------------
29-
// Public
30-
// --------------------------------------------------------------------------
31-
32-
return {
33-
34-
CallExpression: function(node) {
35-
var callee = node.callee;
36-
if (
37-
callee.type !== 'MemberExpression' ||
38-
callee.object.type !== 'ThisExpression' ||
39-
callee.property.name !== 'setState'
40-
) {
41-
return;
42-
}
43-
var ancestors = context.getAncestors(callee).reverse();
44-
var depth = 0;
45-
for (var i = 0, j = ancestors.length; i < j; i++) {
46-
if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) {
47-
depth++;
48-
}
49-
if (
50-
(ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') ||
51-
ancestors[i].key.name !== 'componentDidUpdate' ||
52-
(mode !== 'disallow-in-func' && depth > 1)
53-
) {
54-
continue;
55-
}
56-
context.report({
57-
node: callee,
58-
message: 'Do not use setState in componentDidUpdate'
59-
});
60-
break;
61-
}
62-
}
63-
};
64-
65-
}
66-
};
9+
module.exports = makeNoMethodSetStateRule('componentDidUpdate');

lib/rules/no-will-update-set-state.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @fileoverview Prevent usage of setState in componentWillUpdate
3+
* @author Yannick Croissant
4+
*/
5+
'use strict';
6+
7+
var makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule');
8+
9+
module.exports = makeNoMethodSetStateRule('componentWillUpdate');

lib/util/makeNoMethodSetStateRule.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* @fileoverview Prevent usage of setState in lifecycle methods
3+
* @author Yannick Croissant
4+
*/
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Rule Definition
9+
// ------------------------------------------------------------------------------
10+
11+
function makeNoMethodSetStateRule(methodName) {
12+
return {
13+
meta: {
14+
docs: {
15+
description: 'Prevent usage of setState in ' + methodName,
16+
category: 'Best Practices',
17+
recommended: false
18+
},
19+
20+
schema: [{
21+
enum: ['disallow-in-func']
22+
}]
23+
},
24+
25+
create: function(context) {
26+
27+
var mode = context.options[0] || 'allow-in-func';
28+
29+
// --------------------------------------------------------------------------
30+
// Public
31+
// --------------------------------------------------------------------------
32+
33+
return {
34+
35+
CallExpression: function(node) {
36+
var callee = node.callee;
37+
if (
38+
callee.type !== 'MemberExpression' ||
39+
callee.object.type !== 'ThisExpression' ||
40+
callee.property.name !== 'setState'
41+
) {
42+
return;
43+
}
44+
var ancestors = context.getAncestors(callee).reverse();
45+
var depth = 0;
46+
for (var i = 0, j = ancestors.length; i < j; i++) {
47+
if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) {
48+
depth++;
49+
}
50+
if (
51+
(ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') ||
52+
ancestors[i].key.name !== methodName ||
53+
(mode !== 'disallow-in-func' && depth > 1)
54+
) {
55+
continue;
56+
}
57+
context.report({
58+
node: callee,
59+
message: 'Do not use setState in ' + methodName
60+
});
61+
break;
62+
}
63+
}
64+
};
65+
66+
}
67+
};
68+
}
69+
70+
module.exports = makeNoMethodSetStateRule;

0 commit comments

Comments
 (0)