Skip to content

Commit

Permalink
Allow this.state assignment on constructor
Browse files Browse the repository at this point in the history
see #832
  • Loading branch information
BuraBure committed May 29, 2017
1 parent 7ebcd48 commit 886b7a0
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 15 deletions.
12 changes: 12 additions & 0 deletions docs/rules/no-direct-mutation-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
NEVER mutate `this.state` directly, as calling `setState()` afterwards may replace
the mutation you made. Treat `this.state` as if it were immutable.

The only place that's acceptable to assign this.state is in a ES6 `class` component constructor.

## Rule Details

This rule is aimed to forbid the use of mutating `this.state` directly.
Expand Down Expand Up @@ -34,4 +36,14 @@ var Hello = createReactClass({
return <div>Hello {this.state.name}</div>;
}
});

class Hello extends React.Component {
constructor(props) {
super(props)

this.state = {
foo: 'bar',
}
}
}
```
42 changes: 27 additions & 15 deletions lib/rules/no-direct-mutation-state.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* @fileoverview Prevent direct mutation of this.state
* @author David Petersen
* @author Nicolas Fernandez <@burabure>
*/
'use strict';

var has = require('has');
var Components = require('../util/Components');

// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -49,11 +49,18 @@ module.exports = {
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
var inConstructor = false;

return {
AssignmentExpression: function(node) {
var item;
if (!node.left || !node.left.object || !node.left.object.object) {
MethodDefinition(node) {
if (node.kind === 'constructor') {
inConstructor = true;
}
},

AssignmentExpression(node) {
let item;
if (inConstructor || !node.left || !node.left.object || !node.left.object.object) {
return;
}
item = node.left.object;
Expand All @@ -64,26 +71,31 @@ module.exports = {
item.object.type === 'ThisExpression' &&
item.property.name === 'state'
) {
var component = components.get(utils.getParentComponent());
var mutations = component && component.mutations || [];
const component = components.get(utils.getParentComponent());
const mutations = (component && component.mutations) || [];
mutations.push(node.left.object);
components.set(node, {
mutateSetState: true,
mutations: mutations
mutations
});
}
},

'Program:exit': function() {
var list = components.list();
for (var component in list) {
if (!has(list, component) || isValid(list[component])) {
continue;
}
reportMutations(list[component]);
'MethodDefinition:exit': function (node) {
if (node.kind === 'constructor') {
inConstructor = false;
}
},

'Program:exit': function () {
const list = components.list();

Object.keys(list).forEach((key) => {
if (!isValid(list[key])) {
reportMutations(list[key]);
}
});
}
};

})
};
99 changes: 99 additions & 0 deletions tests/lib/rules/no-direct-mutation-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ ruleTester.run('no-direct-mutation-state', rule, {
' }',
'}'
].join('\n')
}, {
code: [
'class Hello extends React.Component {',
' constructor() {',
' this.state.foo = "bar"',
' }',
'}'
].join('\n')
}],

invalid: [{
Expand Down Expand Up @@ -118,6 +126,97 @@ ruleTester.run('no-direct-mutation-state', rule, {
line: 4,
column: 5
}]
}, {
code: [
'class Hello extends React.Component {',
' constructor() {',
' someFn()',
' }',
' someFn() {',
' this.state.foo = "bar"',
' }',
'}'
].join('\n'),
errors: [{
message: 'Do not mutate state directly. Use setState().'
}]
}, {
code: [
'class Hello extends React.Component {',
' componentWillMount() {',
' this.state.foo = "bar"',
' }',
'}'
].join('\n'),
errors: [{
message: 'Do not mutate state directly. Use setState().'
}]
}, {
code: [
'class Hello extends React.Component {',
' componentDidMount() {',
' this.state.foo = "bar"',
' }',
'}'
].join('\n'),
errors: [{
message: 'Do not mutate state directly. Use setState().'
}]
}, {
code: [
'class Hello extends React.Component {',
' componentWillReceiveProps() {',
' this.state.foo = "bar"',
' }',
'}'
].join('\n'),
errors: [{
message: 'Do not mutate state directly. Use setState().'
}]
}, {
code: [
'class Hello extends React.Component {',
' shouldComponentUpdate() {',
' this.state.foo = "bar"',
' }',
'}'
].join('\n'),
errors: [{
message: 'Do not mutate state directly. Use setState().'
}]
}, {
code: [
'class Hello extends React.Component {',
' componentWillUpdate() {',
' this.state.foo = "bar"',
' }',
'}'
].join('\n'),
errors: [{
message: 'Do not mutate state directly. Use setState().'
}]
}, {
code: [
'class Hello extends React.Component {',
' componentDidUpdate() {',
' this.state.foo = "bar"',
' }',
'}'
].join('\n'),
errors: [{
message: 'Do not mutate state directly. Use setState().'
}]
}, {
code: [
'class Hello extends React.Component {',
' componentWillUnmount() {',
' this.state.foo = "bar"',
' }',
'}'
].join('\n'),
errors: [{
message: 'Do not mutate state directly. Use setState().'
}]
}
/**
* Would be nice to prevent this too
Expand Down

0 comments on commit 886b7a0

Please sign in to comment.