Skip to content
74 changes: 74 additions & 0 deletions docs/rules/state-in-constructor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Enforce state initialization style (react/state-in-constructor)

This rule will enforce the state initialization style to be either in a constructor or with a class property.

## Rule Options

```js
...
"react/state-in-constructor": [<enabled>, <mode>]
...
```

### `always` mode

Will enforce the state initialization style to be in a constructor. This is the default mode.

The following patterns are considered warnings:

```jsx
class Foo extends React.Component {
state = { bar: 0 }
render() {
return <div>Foo</div>
}
}
```

The following patterns are **not** considered warnings:

```jsx
class Foo extends React.Component {
constructor(props) {
super(props)
this.state = { bar: 0 }
}
render() {
return <div>Foo</div>
}
}
```

### `never` mode

Will enforce the state initialization style to be with a class property.

The following patterns are considered warnings:

```jsx
class Foo extends React.Component {
constructor(props) {
super(props)
this.state = { bar: 0 }
}
render() {
return <div>Foo</div>
}
}
```

The following patterns are **not** considered warnings:

```jsx
class Foo extends React.Component {
state = { bar: 0 }
render() {
return <div>Foo</div>
}
}
```


## When Not To Use It

When the way a component state is being initialized doesn't matter.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const allRules = {
'self-closing-comp': require('./lib/rules/self-closing-comp'),
'sort-comp': require('./lib/rules/sort-comp'),
'sort-prop-types': require('./lib/rules/sort-prop-types'),
'state-in-constructor': require('./lib/rules/state-in-constructor'),
'style-prop-object': require('./lib/rules/style-prop-object'),
'void-dom-elements-no-children': require('./lib/rules/void-dom-elements-no-children')
};
Expand Down
59 changes: 59 additions & 0 deletions lib/rules/state-in-constructor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @fileoverview Enforce the state initialization style to be either in a constructor or with a class property
* @author Kanitkorn Sujautra
*/
'use strict';

const Components = require('../util/Components');
const docsUrl = require('../util/docsUrl');

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: 'State initialization in an ES6 class component should be in a constructor',
category: 'Stylistic Issues',
recommended: false,
url: docsUrl('state-in-constructor')
},
schema: [{
enum: ['always', 'never']
}]
},

create: Components.detect((context, components, utils) => {
const option = context.options[0] || 'always';
return {
ClassProperty: function (node) {
if (
option === 'always' &&
!node.static &&
node.key.name === 'state' &&
utils.isES6Component(node.parent.parent)
) {
context.report({
node,
message: 'State initialization should be in a constructor'
});
}
},
AssignmentExpression(node) {
if (
option === 'never' &&
node.left.object.type === 'ThisExpression' &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two lines assume that node.left is a MemberExpression which is not always the case in an AssignmentExpression.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to add a test case that would fail here.

node.left.property.name === 'state' &&
node.parent.parent.parent.parent.kind === 'constructor' &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit brittle: assignment could be nested in other nodes (e.g. if conditions) in which case this sequence will not work. This should either be traversing parents in a loop, or scopes like https://github.com/yannickcr/eslint-plugin-react/blob/master/lib/rules/no-unused-prop-types.js#L264 (although not sure which one is better)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to add a test case that would fail here.

utils.isES6Component(node.parent.parent.parent.parent.parent.parent)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, but in this case maybe utils.getParentES6Component would work

) {
context.report({
node,
message: 'State initialization should be in a class property'
});
}
}
};
})
};
Loading