Skip to content

Commit a9f0b95

Browse files
ngtanljharb
authored andcommitted
[New] add no-arrow-function-lifecycle
1 parent 1371f71 commit a9f0b95

File tree

9 files changed

+1200
-19
lines changed

9 files changed

+1200
-19
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
77

88
### Added
99
* [`no-unused-class-component-methods`]: Handle unused class component methods ([#2166][] @jakeleventhal @pawelnvk)
10+
* add [`no-arrow-function-lifecycle`] ([#1980][] @ngtan)
1011

1112
[#2166]: https://github.com/yannickcr/eslint-plugin-react/pull/2166
13+
[#1980]: https://github.com/yannickcr/eslint-plugin-react/pull/1980
1214

1315
## [7.26.1] - 2021.09.29
1416

@@ -3465,6 +3467,7 @@ If you're still not using React 15 you can keep the old behavior by setting the
34653467
[`no-access-state-in-setstate`]: docs/rules/no-access-state-in-setstate.md
34663468
[`no-adjacent-inline-elements`]: docs/rules/no-adjacent-inline-elements.md
34673469
[`no-array-index-key`]: docs/rules/no-array-index-key.md
3470+
[`no-arrow-function-lifecycle`]: docs/rules/no-arrow-function-lifecycle.md
34683471
[`no-children-prop`]: docs/rules/no-children-prop.md
34693472
[`no-comment-textnodes`]: docs/rules/jsx-no-comment-textnodes.md
34703473
[`no-danger-with-children`]: docs/rules/no-danger-with-children.md

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ Enable the rules that you would like to use.
135135
| | | [react/no-access-state-in-setstate](docs/rules/no-access-state-in-setstate.md) | Reports when this.state is accessed within setState |
136136
| | | [react/no-adjacent-inline-elements](docs/rules/no-adjacent-inline-elements.md) | Prevent adjacent inline elements not separated by whitespace. |
137137
| | | [react/no-array-index-key](docs/rules/no-array-index-key.md) | Prevent usage of Array index in keys |
138+
| | 🔧 | [react/no-arrow-function-lifecycle](docs/rules/no-arrow-function-lifecycle.md) | Lifecycle methods should be methods on the prototype, not class fields |
138139
|| | [react/no-children-prop](docs/rules/no-children-prop.md) | Prevent passing of children as props. |
139140
| | | [react/no-danger](docs/rules/no-danger.md) | Prevent usage of dangerous JSX props |
140141
|| | [react/no-danger-with-children](docs/rules/no-danger-with-children.md) | Report when a DOM element is using both children and dangerouslySetInnerHTML |
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Lifecycle methods should be methods on the prototype, not class fields (react/no-arrow-function-lifecycle)
2+
3+
It is not neccessary to use arrow function for lifecycle methods. This makes things harder to test, conceptually less performant (although in practice, performance will not be affected, since most engines will optimize efficiently), and can break hot reloading patterns.
4+
5+
## Rule Details
6+
7+
The following patterns are considered warnings:
8+
9+
```jsx
10+
class Hello extends React.Component {
11+
render = () => {
12+
return <div />;
13+
}
14+
}
15+
16+
var AnotherHello = createReactClass({
17+
render: () => {
18+
return <div />;
19+
},
20+
});
21+
```
22+
23+
The following patterns are **not** considered warnings:
24+
25+
```jsx
26+
class Hello extends React.Component {
27+
render() {
28+
return <div />;
29+
}
30+
}
31+
32+
var AnotherHello = createReactClass({
33+
render() {
34+
return <div />;
35+
},
36+
});
37+
38+
```
39+
## When Not To Use It
40+
41+
If you don't care about performance of your application or conceptual correctness of class property placement, you can disable this rule.

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const allRules = {
5757
'no-access-state-in-setstate': require('./lib/rules/no-access-state-in-setstate'),
5858
'no-adjacent-inline-elements': require('./lib/rules/no-adjacent-inline-elements'),
5959
'no-array-index-key': require('./lib/rules/no-array-index-key'),
60+
'no-arrow-function-lifecycle': require('./lib/rules/no-arrow-function-lifecycle'),
6061
'no-children-prop': require('./lib/rules/no-children-prop'),
6162
'no-danger': require('./lib/rules/no-danger'),
6263
'no-danger-with-children': require('./lib/rules/no-danger-with-children'),
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @fileoverview Lifecycle methods should be methods on the prototype, not class fields
3+
* @author Tan Nguyen
4+
*/
5+
6+
'use strict';
7+
8+
const values = require('object.values');
9+
10+
const Components = require('../util/Components');
11+
const astUtil = require('../util/ast');
12+
const docsUrl = require('../util/docsUrl');
13+
const lifecycleMethods = require('../util/lifecycleMethods');
14+
15+
module.exports = {
16+
meta: {
17+
docs: {
18+
description: 'Lifecycle methods should be methods on the prototype, not class fields',
19+
category: 'Best Practices',
20+
recommended: false,
21+
url: docsUrl('no-arrow-function-lifecycle')
22+
},
23+
schema: [],
24+
fixable: 'code'
25+
},
26+
27+
create: Components.detect((context, components, utils) => {
28+
function getText(node) {
29+
const params = node.value.params.map((p) => p.name);
30+
31+
if (node.type === 'Property') {
32+
return `: function(${params.join(', ')}) `;
33+
}
34+
35+
if (node.type === 'ClassProperty') {
36+
return `(${params.join(', ')}) `;
37+
}
38+
39+
return null;
40+
}
41+
42+
/**
43+
* @param {Array} properties list of component properties
44+
*/
45+
function reportNoArrowFunctionLifecycle(properties) {
46+
properties.forEach((node) => {
47+
const propertyName = astUtil.getPropertyName(node);
48+
const nodeType = node.value.type;
49+
const isLifecycleMethod = (
50+
node.static && !utils.isES5Component(node)
51+
? lifecycleMethods.static
52+
: lifecycleMethods.instance
53+
).indexOf(propertyName) > -1;
54+
55+
if (nodeType === 'ArrowFunctionExpression' && isLifecycleMethod) {
56+
const range = [node.key.range[1], node.value.body.range[0]];
57+
const text = getText(node);
58+
59+
context.report({
60+
node,
61+
message: '{{propertyName}} is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.',
62+
data: {
63+
propertyName
64+
},
65+
fix: (fixer) => fixer.replaceTextRange(range, text)
66+
});
67+
}
68+
});
69+
}
70+
71+
return {
72+
'Program:exit'() {
73+
values(components.list()).forEach((component) => {
74+
const properties = astUtil.getComponentProperties(component.node);
75+
reportNoArrowFunctionLifecycle(properties);
76+
});
77+
}
78+
};
79+
})
80+
};

lib/rules/no-typos.js

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,13 @@ const PROP_TYPES = Object.keys(require('prop-types'));
88
const Components = require('../util/Components');
99
const docsUrl = require('../util/docsUrl');
1010
const report = require('../util/report');
11+
const lifecycleMethods = require('../util/lifecycleMethods');
1112

1213
// ------------------------------------------------------------------------------
1314
// Rule Definition
1415
// ------------------------------------------------------------------------------
1516

1617
const STATIC_CLASS_PROPERTIES = ['propTypes', 'contextTypes', 'childContextTypes', 'defaultProps'];
17-
const STATIC_LIFECYCLE_METHODS = ['getDerivedStateFromProps'];
18-
const LIFECYCLE_METHODS = [
19-
'getDerivedStateFromProps',
20-
'componentWillMount',
21-
'UNSAFE_componentWillMount',
22-
'componentDidMount',
23-
'componentWillReceiveProps',
24-
'UNSAFE_componentWillReceiveProps',
25-
'shouldComponentUpdate',
26-
'componentWillUpdate',
27-
'UNSAFE_componentWillUpdate',
28-
'getSnapshotBeforeUpdate',
29-
'componentDidUpdate',
30-
'componentDidCatch',
31-
'componentWillUnmount',
32-
'render'
33-
];
3418

3519
const messages = {
3620
typoPropTypeChain: 'Typo in prop type chain qualifier: {{name}}',
@@ -167,7 +151,7 @@ module.exports = {
167151
return;
168152
}
169153

170-
STATIC_LIFECYCLE_METHODS.forEach((method) => {
154+
lifecycleMethods.static.forEach((method) => {
171155
if (!node.static && nodeKeyName.toLowerCase() === method.toLowerCase()) {
172156
report(context, messages.staticLifecycleMethod, 'staticLifecycleMethod', {
173157
node,
@@ -178,7 +162,7 @@ module.exports = {
178162
}
179163
});
180164

181-
LIFECYCLE_METHODS.forEach((method) => {
165+
lifecycleMethods.instance.concat(lifecycleMethods.static).forEach((method) => {
182166
if (method.toLowerCase() === nodeKeyName.toLowerCase() && method !== nodeKeyName) {
183167
report(context, messages.typoLifecycleMethod, 'typoLifecycleMethod', {
184168
node,

lib/util/lifecycleMethods.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* @fileoverview lifecycle methods
3+
* @author Tan Nguyen
4+
*/
5+
6+
'use strict';
7+
8+
module.exports = {
9+
instance: [
10+
'getDefaultProps',
11+
'getInitialState',
12+
'getChildContext',
13+
'componentWillMount',
14+
'UNSAFE_componentWillMount',
15+
'componentDidMount',
16+
'componentWillReceiveProps',
17+
'UNSAFE_componentWillReceiveProps',
18+
'shouldComponentUpdate',
19+
'componentWillUpdate',
20+
'UNSAFE_componentWillUpdate',
21+
'getSnapshotBeforeUpdate',
22+
'componentDidUpdate',
23+
'componentDidCatch',
24+
'componentWillUnmount',
25+
'render'
26+
],
27+
static: [
28+
'getDerivedStateFromProps'
29+
]
30+
};

0 commit comments

Comments
 (0)