Skip to content

Commit caa90bb

Browse files
feat: mapStateToProps-no-store
* feat: mapStateToProps-no-store
1 parent c28da6c commit caa90bb

File tree

8 files changed

+208
-3
lines changed

8 files changed

+208
-3
lines changed

.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ module.exports = {
88
rules: {
99
"func-names": 0,
1010
"global-require": 0,
11-
"prefer-destructuring": 0
11+
"prefer-destructuring": 0,
12+
"strict": 0
1213
},
1314
"env": {
1415
mocha: true

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,6 @@ To configure individual rules:
5050
* [react-redux/connect-prefer-named-arguments](docs/rules/connect-prefer-named-arguments.md) Enforces that all connect arguments have specific names.
5151
* [react-redux/mapDispatchToProps-prefer-object](docs/rules/mapDispatchToProps-prefer-object.md) Enforces that all mapDispatchToProps parameters have specific names.
5252
* [react-redux/mapDispatchToProps-prefer-parameters-names](docs/rules/mapDispatchToProps-prefer-parameters-names.md) Enforces that mapDispatchToProps returns an object.
53+
* [react-redux/mapStateToProps-no-store](docs/rules/mapStateToProps-no-store.md) Prohibits binding a whole store object to a component.
5354
* [react-redux/mapStateToProps-prefer-parameters-names](docs/rules/mapStateToProps-prefer-parameters-names.md) Enforces that all mapStateToProps parameters have specific names.
5455
* [react-redux/prefer-separate-component-file](docs/rules/prefer-separate-component-file.md) Enforces that all connected components are defined in a separate file.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Enforces that mapStateToProps does not bind complete store to a component. (react-redux/mapStateToProps-no-store)
2+
3+
Passing whole state to a component is a bd practice. Triggering unnecessary re-renders.
4+
Instead one should provide specific properties used by a component.
5+
6+
## Rule details
7+
8+
The following patterns are considered incorrect:
9+
10+
```js
11+
const mapStateToProps = (state) => state
12+
```
13+
14+
```js
15+
const mapStateToProps = state => {
16+
return {state: state}
17+
}
18+
```
19+
20+
```js
21+
connect((state) => state, null)(App)
22+
```
23+
24+
The following patterns are correct:
25+
26+
```js
27+
const mapStateToProps = () => {}
28+
```
29+
30+
```js
31+
const mapStateToProps = (state) => {isActive: state.isActive}
32+
```
33+
34+
```js
35+
connect((state) => ({isActive: state.isActive}), null)(App)
36+
```

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const rules = {
33
'connect-prefer-named-arguments': require('./lib/rules/connect-prefer-named-arguments'),
44
'mapDispatchToProps-prefer-object': require('./lib/rules/mapDispatchToProps-prefer-object'),
55
'mapDispatchToProps-prefer-parameters-names': require('./lib/rules/mapDispatchToProps-prefer-parameters-names'),
6+
'mapStateToProps-no-store': require('./lib/rules/mapStateToProps-no-store'),
67
'mapStateToProps-prefer-parameters-names': require('./lib/rules/mapStateToProps-prefer-parameters-names'),
78
'prefer-separate-component-file': require('./lib/rules/prefer-separate-component-file'),
89
};
@@ -27,6 +28,7 @@ module.exports = {
2728
'react-redux/connect-prefer-named-arguments': 2,
2829
'react-redux/mapDispatchToProps-prefer-parameters-names': 2,
2930
'react-redux/mapDispatchToProps-prefer-object': 2,
31+
'react-redux/mapStateToProps-no-store': 2,
3032
'react-redux/mapStateToProps-prefer-parameters-names': 2,
3133
'react-redux/prefer-separate-component-file': 1,
3234
},

lib/rules/mapStateToProps-no-store.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
const utils = require('../utils');
2+
const isReactReduxConnect = require('../isReactReduxConnect');
3+
4+
const report = function (context, node) {
5+
context.report({
6+
message: 'mapStateToProps should not return complete store object',
7+
node,
8+
});
9+
};
10+
11+
const getFirstParamName = node =>
12+
node.params && node.params[0] && node.params[0].name;
13+
14+
const checkFunction = function (context, body, firstParamName) {
15+
const returnNode = utils.getReturnNode(body);
16+
// return state;
17+
if (returnNode && returnNode.type === 'Identifier' && returnNode.name === firstParamName) {
18+
report(context, body);
19+
}
20+
// return {store: state};
21+
if (returnNode && returnNode.type === 'ObjectExpression' &&
22+
returnNode.properties.reduce((acc, cv) =>
23+
acc || (cv.value.name === firstParamName), false)) {
24+
report(context, body);
25+
}
26+
};
27+
28+
module.exports = function (context) {
29+
return {
30+
VariableDeclaration(node) {
31+
node.declarations.forEach((decl) => {
32+
if (decl.id && decl.id.name === 'mapStateToProps') {
33+
const body = decl.init.body;
34+
const firxtParamName = decl.init.params &&
35+
decl.init.params[0] &&
36+
decl.init.params[0].name;
37+
checkFunction(context, body, firxtParamName);
38+
}
39+
});
40+
},
41+
FunctionDeclaration(node) {
42+
checkFunction(context, node.body, getFirstParamName(node));
43+
},
44+
CallExpression(node) {
45+
if (isReactReduxConnect(node)) {
46+
const mapStateToProps = node.arguments && node.arguments[0];
47+
checkFunction(context, mapStateToProps.body, getFirstParamName(mapStateToProps));
48+
}
49+
},
50+
};
51+
};

lib/utils.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict';
2+
3+
const getReturnNode = (node) => {
4+
const body = node.body;
5+
if (!body || !body.length) {
6+
return node;
7+
}
8+
for (let i = body.length - 1; i >= 0; i -= 1) {
9+
if (body[i].type === 'ReturnStatement') {
10+
return body[i].argument;
11+
}
12+
}
13+
return null;
14+
};
15+
16+
module.exports = {
17+
getReturnNode,
18+
};

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
"scripts": {
1414
"lint": "eslint ./",
1515
"test": "npm run lint && mocha tests --recursive",
16-
"test-single": "mocha tests/lib/rules/prefer-separate-component-file",
17-
"debug-test": "mocha --debug-brk --inspect tests/index.js",
16+
"test-single": "mocha tests/lib/rules/mapStateToProps-no-store",
17+
"debug-test": "mocha --debug-brk --inspect tests/lib/rules/mapStateToProps-no-store",
1818
"semantic-release": "semantic-release",
1919
"commitmsg": "npm run test && commitlint -e $GIT_PARAMS"
2020
},
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
require('babel-eslint');
2+
3+
const rule = require('../../../lib/rules/mapStateToProps-no-store');
4+
const RuleTester = require('eslint').RuleTester;
5+
6+
const parserOptions = {
7+
ecmaVersion: 6,
8+
sourceType: 'module',
9+
ecmaFeatures: {
10+
experimentalObjectRestSpread: true,
11+
},
12+
};
13+
14+
const ruleTester = new RuleTester({ parserOptions });
15+
16+
ruleTester.run('mapStateToProps-no-store', rule, {
17+
valid: [
18+
'connect((state) => ({isActive: state.isActive}), null)(App)',
19+
`connect(
20+
(state) => {
21+
return {
22+
isActive: state.isActive
23+
}
24+
},
25+
null
26+
)(App)
27+
`,
28+
`connect(function(state){
29+
return {
30+
isActive: state.isActive
31+
}
32+
},
33+
null
34+
)(App)
35+
`,
36+
`function mapStateToProps(state) {
37+
return {};
38+
}`,
39+
`const mapStateToProps = function(state) {
40+
return state.isActive;
41+
}`,
42+
'const mapStateToProps = (state, ownProps) => {}',
43+
'const mapStateToProps = (state) => {isActive: state.isActive}',
44+
],
45+
invalid: [{
46+
code: 'const mapStateToProps = (state) => state',
47+
errors: [
48+
{
49+
message: 'mapStateToProps should not return complete store object',
50+
},
51+
],
52+
}, {
53+
code: `const mapStateToProps = state => {
54+
return {state: state}
55+
}`,
56+
errors: [
57+
{
58+
message: 'mapStateToProps should not return complete store object',
59+
},
60+
],
61+
}, {
62+
code: `function mapStateToProps(state) {
63+
return state;
64+
}`,
65+
errors: [
66+
{
67+
message: 'mapStateToProps should not return complete store object',
68+
},
69+
],
70+
}, {
71+
code: `export default connect(
72+
(state) => {
73+
return {
74+
state: state
75+
}
76+
},
77+
(dispatch) => {
78+
return {
79+
actions: bindActionCreators(actions, dispatch)
80+
}
81+
}
82+
)(App)`,
83+
errors: [
84+
{
85+
message: 'mapStateToProps should not return complete store object',
86+
},
87+
],
88+
}, {
89+
code: 'connect((state) => state, null)(App)',
90+
errors: [
91+
{
92+
message: 'mapStateToProps should not return complete store object',
93+
},
94+
],
95+
}],
96+
});

0 commit comments

Comments
 (0)