Skip to content

Commit 80fb96c

Browse files
committed
Add new rule for preventing this in SFCs
This rule will report errors when a stateless functional component is attempting to use `this`.
1 parent e2ca690 commit 80fb96c

File tree

6 files changed

+219
-1
lines changed

6 files changed

+219
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
108108
* [react/no-set-state](docs/rules/no-set-state.md): Prevent usage of `setState`
109109
* [react/no-typos](docs/rules/no-typos.md): Prevent common casing typos
110110
* [react/no-string-refs](docs/rules/no-string-refs.md): Prevent using string references in `ref` attribute.
111+
* [react/no-this-in-sfc](docs/rules/no-this-in-sfc.md): Prevent using `this` in stateless functional components
111112
* [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md): Prevent invalid characters from appearing in markup
112113
* [react/no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property (fixable)
113114
* [react/no-unused-prop-types](docs/rules/no-unused-prop-types.md): Prevent definitions of unused prop types

docs/rules/no-this-in-sfc.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Prevent `this` from being used in stateless functional components (react/no-this-in-sfc)
2+
3+
When using a stateless functional component (SFC), props/context aren't accessed in the same way as a class component or the `create-react-class` format. Both props and context are passed as separate arguments to the component instead. Also, as the name suggests, a stateless component does not have state on `this.state`.
4+
5+
Attempting to access properties on `this` can be a potential error if someone is unaware of the differences when writing a SFC or missed when converting a class component to a SFC.
6+
7+
8+
## Rule Details
9+
10+
The following patterns are considered warnings:
11+
12+
```jsx
13+
function Foo(props) {
14+
return (
15+
<div>{this.props.bar}</div>
16+
);
17+
}
18+
```
19+
20+
```jsx
21+
function Foo(props, context) {
22+
return (
23+
<div>
24+
{this.context.foo ? this.props.bar : ''}
25+
</div>
26+
);
27+
}
28+
```
29+
30+
```jsx
31+
function Foo(props) {
32+
if (this.state.loading) {
33+
return <Loader />;
34+
}
35+
return (
36+
<div>
37+
{this.props.bar}
38+
</div>
39+
);
40+
}
41+
```
42+
43+
The following patterns are **not** considered warnings:
44+
45+
```jsx
46+
function Foo(props) {
47+
return (
48+
<div>{props.bar}</div>
49+
);
50+
}
51+
```
52+
53+
```jsx
54+
function Foo(props, context) {
55+
return (
56+
<div>
57+
{context.foo ? props.bar : ''}
58+
</div>
59+
);
60+
}
61+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const allRules = {
5555
'no-string-refs': require('./lib/rules/no-string-refs'),
5656
'no-redundant-should-component-update': require('./lib/rules/no-redundant-should-component-update'),
5757
'no-render-return-value': require('./lib/rules/no-render-return-value'),
58+
'no-this-in-sfc': require('./lib/rules/no-this-in-sfc'),
5859
'no-typos': require('./lib/rules/no-typos'),
5960
'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'),
6061
'no-unknown-property': require('./lib/rules/no-unknown-property'),

lib/rules/no-this-in-sfc.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @fileoverview Report "this" being used in stateless functional components.
3+
*/
4+
'use strict';
5+
6+
const Components = require('../util/Components');
7+
8+
// ------------------------------------------------------------------------------
9+
// Constants
10+
// ------------------------------------------------------------------------------
11+
12+
const ERROR_MESSAGE = 'Stateless functional components should not use this';
13+
14+
// ------------------------------------------------------------------------------
15+
// Rule Definition
16+
// ------------------------------------------------------------------------------
17+
18+
module.exports = {
19+
meta: {
20+
docs: {
21+
description: 'Report "this" being used in stateless components',
22+
category: 'Possible Errors',
23+
recommended: false
24+
},
25+
schema: []
26+
},
27+
28+
create: Components.detect((context, components, utils) => ({
29+
MemberExpression(node) {
30+
const component = components.get(utils.getParentStatelessComponent());
31+
if (!component) {
32+
return;
33+
}
34+
if (node.object.type === 'ThisExpression') {
35+
context.report({
36+
node: node,
37+
message: ERROR_MESSAGE
38+
});
39+
}
40+
}
41+
}))
42+
};

lib/util/Components.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,13 @@ function componentRule(rule, context) {
319319
break;
320320
case 'ArrowFunctionExpression':
321321
property = 'body';
322+
if (node[property] && node[property].type === 'BlockStatement') {
323+
node = utils.findReturnStatement(node);
324+
if (!node) {
325+
return false;
326+
}
327+
property = 'argument';
328+
}
322329
break;
323330
default:
324331
node = utils.findReturnStatement(node);
@@ -430,7 +437,7 @@ function componentRule(rule, context) {
430437
return null;
431438
}
432439
// Return the node if it is a function that is not a class method and is not inside a JSX Element
433-
if (isFunction && !isMethod && !isJSXExpressionContainer) {
440+
if (isFunction && !isMethod && !isJSXExpressionContainer && utils.isReturningJSX(node)) {
434441
return node;
435442
}
436443
scope = scope.upper;

tests/lib/rules/no-this-in-sfc.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* @fileoverview Report "this" being used in stateless functional components.
3+
*/
4+
'use strict';
5+
6+
// ------------------------------------------------------------------------------
7+
// Constants
8+
// ------------------------------------------------------------------------------
9+
10+
const ERROR_MESSAGE = 'Stateless functional components should not use this';
11+
12+
// ------------------------------------------------------------------------------
13+
// Requirements
14+
// ------------------------------------------------------------------------------
15+
16+
const rule = require('../../../lib/rules/no-this-in-sfc');
17+
const RuleTester = require('eslint').RuleTester;
18+
19+
const parserOptions = {
20+
ecmaVersion: 8,
21+
sourceType: 'module',
22+
ecmaFeatures: {
23+
experimentalObjectRestSpread: true,
24+
jsx: true
25+
}
26+
};
27+
28+
const ruleTester = new RuleTester({parserOptions});
29+
ruleTester.run('no-this-in-sfc', rule, {
30+
valid: [{
31+
code: `
32+
function Foo(props) {
33+
const { foo } = props;
34+
return <div bar={foo} />;
35+
}`
36+
}, {
37+
code: `
38+
class Foo extends React.Component {
39+
render() {
40+
const { foo } = this.props;
41+
return <div bar={foo} />;
42+
}
43+
}`
44+
}, {
45+
code: `
46+
const Foo = createReactClass({
47+
render: function() {
48+
return <div>{this.props.foo}</div>;
49+
}
50+
});`
51+
}, {
52+
code: `
53+
const Foo = React.createClass({
54+
render: function() {
55+
return <div>{this.props.foo}</div>;
56+
}
57+
});`,
58+
settings: {react: {createClass: 'createClass'}}
59+
}, {
60+
code: `
61+
function Foo (bar) {
62+
this.bar = bar;
63+
this.props = 'baz';
64+
this.getFoo = function() {
65+
return this.bar + this.props;
66+
}
67+
}
68+
`
69+
}],
70+
invalid: [{
71+
code: `
72+
function Foo(props) {
73+
const { foo } = this.props;
74+
return <div>{foo}</div>;
75+
}`,
76+
errors: [{message: ERROR_MESSAGE}]
77+
}, {
78+
code: `
79+
function Foo(props) {
80+
return <div>{this.props.foo}</div>;
81+
}`,
82+
errors: [{message: ERROR_MESSAGE}]
83+
}, {
84+
code: `
85+
function Foo(props) {
86+
return <div>{this.state.foo}</div>;
87+
}`,
88+
errors: [{message: ERROR_MESSAGE}]
89+
}, {
90+
code: `
91+
function Foo(props) {
92+
const { foo } = this.state;
93+
return <div>{foo}</div>;
94+
}`,
95+
errors: [{message: ERROR_MESSAGE}]
96+
}, {
97+
code: `
98+
function Foo(props) {
99+
function onClick(bar) {
100+
this.props.onClick();
101+
}
102+
return <div onClick={onClick}>{this.props.foo}</div>;
103+
}`,
104+
errors: [{message: ERROR_MESSAGE}, {message: ERROR_MESSAGE}]
105+
}]
106+
});

0 commit comments

Comments
 (0)