Skip to content

Commit 87430bd

Browse files
committed
Merge pull request #223 from petersendidit/no-direct-mutation-state
Add no-direct-mutation-state rule (fixes #133, fixes #201)
2 parents 87eb392 + cf6e8fc commit 87430bd

File tree

5 files changed

+203
-2
lines changed

5 files changed

+203
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Finally, enable all of the rules that you would like to use.
6363
"react/no-danger": 1,
6464
"react/no-did-mount-set-state": 1,
6565
"react/no-did-update-set-state": 1,
66+
"react/no-direct-mutation-state": 1,
6667
"react/no-multi-comp": 1,
6768
"react/no-set-state": 1,
6869
"react/no-unknown-property": 1,
@@ -95,6 +96,7 @@ Finally, enable all of the rules that you would like to use.
9596
* [no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
9697
* [no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of `setState` in `componentDidMount`
9798
* [no-did-update-set-state](docs/rules/no-did-update-set-state.md): Prevent usage of `setState` in `componentDidUpdate`
99+
* [no-direct-mutation-state](docs/rules/no-direct-mutation-state.md): Prevent direct mutation of `this.state`
98100
* [no-multi-comp](docs/rules/no-multi-comp.md): Prevent multiple component definition per file
99101
* [no-set-state](docs/rules/no-set-state.md): Prevent usage of `setState`
100102
* [no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Prevent direct mutation of this.state (no-direct-mutation-state)
2+
3+
NEVER mutate `this.state` directly, as calling `setState()` afterwards may replace
4+
the mutation you made. Treat `this.state` as if it were immutable.
5+
6+
## Rule Details
7+
8+
This rule is aimed to forbid the use of mutating `this.state` directly.
9+
10+
The following patterns are considered warnings:
11+
12+
```js
13+
var Hello = React.createClass({
14+
componentDidMount: function() {
15+
this.state.name = this.props.name.toUpperCase();
16+
},
17+
render: function() {
18+
return <div>Hello {this.state.name}</div>;
19+
}
20+
});
21+
```
22+
23+
24+
The following patterns are not considered warnings:
25+
26+
```js
27+
var Hello = React.createClass({
28+
componentDidMount: function() {
29+
this.setState({
30+
name: this.props.name.toUpperCase();
31+
});
32+
},
33+
render: function() {
34+
return <div>Hello {this.state.name}</div>;
35+
}
36+
});
37+
```

index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ module.exports = {
2727
'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'),
2828
'jsx-no-literals': require('./lib/rules/jsx-no-literals'),
2929
'jsx-indent-props': require('./lib/rules/jsx-indent-props'),
30-
'jsx-closing-bracket-location': require('./lib/rules/jsx-closing-bracket-location')
30+
'jsx-closing-bracket-location': require('./lib/rules/jsx-closing-bracket-location'),
31+
'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state')
3132
},
3233
rulesConfig: {
3334
'jsx-uses-react': 0,
@@ -55,6 +56,7 @@ module.exports = {
5556
'jsx-max-props-per-line': 0,
5657
'jsx-no-literals': 0,
5758
'jsx-indent-props': 0,
58-
'jsx-closing-bracket-location': 0
59+
'jsx-closing-bracket-location': 0,
60+
'no-direct-mutation-state': 0
5961
}
6062
};

lib/rules/no-direct-mutation-state.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @fileoverview Prevent usage of setState in componentDidMount
3+
* @author David Petersen
4+
*/
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Rule Definition
9+
// ------------------------------------------------------------------------------
10+
11+
module.exports = function(context) {
12+
13+
// --------------------------------------------------------------------------
14+
// Public
15+
// --------------------------------------------------------------------------
16+
17+
return {
18+
19+
AssignmentExpression: function(node) {
20+
var item;
21+
if (!node.left || !node.left.object || !node.left.object.object) {
22+
return;
23+
}
24+
item = node.left.object;
25+
while (item.object.property) {
26+
item = item.object;
27+
}
28+
if (
29+
item.object.type === 'ThisExpression' &&
30+
item.property.name === 'state'
31+
) {
32+
context.report(node.left.object, 'Do not mutate state directly. Use setState().');
33+
}
34+
}
35+
};
36+
37+
};
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* @fileoverview Prevent direct mutation of this.state
3+
* @author David Petersen
4+
*/
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
var rule = require('../../../lib/rules/no-direct-mutation-state');
12+
var RuleTester = require('eslint').RuleTester;
13+
14+
require('babel-eslint');
15+
16+
// ------------------------------------------------------------------------------
17+
// Tests
18+
// ------------------------------------------------------------------------------
19+
20+
var ruleTester = new RuleTester();
21+
ruleTester.run('no-direct-mutation-state', rule, {
22+
23+
valid: [{
24+
code: [
25+
'var Hello = React.createClass({',
26+
' render: function() {',
27+
' return <div>Hello {this.props.name}</div>;',
28+
' }',
29+
'});'
30+
].join('\n'),
31+
ecmaFeatures: {
32+
jsx: true
33+
}
34+
}, {
35+
code: [
36+
'var Hello = React.createClass({',
37+
' render: function() {',
38+
' var obj = {state: {}};',
39+
' obj.state.name = "foo";',
40+
' return <div>Hello {obj.state.name}</div>;',
41+
' }',
42+
'});'
43+
].join('\n'),
44+
ecmaFeatures: {
45+
jsx: true
46+
}
47+
}, {
48+
code: [
49+
'var Hello = "foo";',
50+
'module.exports = {};'
51+
].join('\n'),
52+
ecmaFeatures: {
53+
jsx: true
54+
}
55+
}],
56+
57+
invalid: [{
58+
code: [
59+
'var Hello = React.createClass({',
60+
' render: function() {',
61+
' this.state.foo = "bar"',
62+
' return <div>Hello {this.props.name}</div>;',
63+
' }',
64+
'});'
65+
].join('\n'),
66+
ecmaFeatures: {
67+
jsx: true
68+
},
69+
errors: [{
70+
message: 'Do not mutate state directly. Use setState().'
71+
}]
72+
}, {
73+
code: [
74+
'var Hello = React.createClass({',
75+
' render: function() {',
76+
' this.state.person.name= "bar"',
77+
' return <div>Hello {this.props.name}</div>;',
78+
' }',
79+
'});'
80+
].join('\n'),
81+
ecmaFeatures: {
82+
jsx: true
83+
},
84+
errors: [{
85+
message: 'Do not mutate state directly. Use setState().'
86+
}]
87+
}, {
88+
code: [
89+
'var Hello = React.createClass({',
90+
' render: function() {',
91+
' this.state.person.name.first = "bar"',
92+
' return <div>Hello</div>;',
93+
' }',
94+
'});'
95+
].join('\n'),
96+
ecmaFeatures: {
97+
jsx: true
98+
},
99+
errors: [{
100+
message: 'Do not mutate state directly. Use setState().'
101+
}]
102+
}
103+
/**
104+
* Would be nice to prevent this too
105+
, {
106+
code: [
107+
'var Hello = React.createClass({',
108+
' render: function() {',
109+
' var that = this;',
110+
' that.state.person.name.first = "bar"',
111+
' return <div>Hello</div>;',
112+
' }',
113+
'});'
114+
].join('\n'),
115+
ecmaFeatures: {
116+
jsx: true
117+
},
118+
errors: [{
119+
message: 'Do not mutate state directly. Use setState().'
120+
}]
121+
}*/
122+
]
123+
});

0 commit comments

Comments
 (0)