Skip to content

Commit

Permalink
feat: mapStateToProps-no-store
Browse files Browse the repository at this point in the history
* feat: mapStateToProps-no-store
  • Loading branch information
DianaSuvorova authored Jan 23, 2018
1 parent c28da6c commit caa90bb
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ module.exports = {
rules: {
"func-names": 0,
"global-require": 0,
"prefer-destructuring": 0
"prefer-destructuring": 0,
"strict": 0
},
"env": {
mocha: true
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ To configure individual rules:
* [react-redux/connect-prefer-named-arguments](docs/rules/connect-prefer-named-arguments.md) Enforces that all connect arguments have specific names.
* [react-redux/mapDispatchToProps-prefer-object](docs/rules/mapDispatchToProps-prefer-object.md) Enforces that all mapDispatchToProps parameters have specific names.
* [react-redux/mapDispatchToProps-prefer-parameters-names](docs/rules/mapDispatchToProps-prefer-parameters-names.md) Enforces that mapDispatchToProps returns an object.
* [react-redux/mapStateToProps-no-store](docs/rules/mapStateToProps-no-store.md) Prohibits binding a whole store object to a component.
* [react-redux/mapStateToProps-prefer-parameters-names](docs/rules/mapStateToProps-prefer-parameters-names.md) Enforces that all mapStateToProps parameters have specific names.
* [react-redux/prefer-separate-component-file](docs/rules/prefer-separate-component-file.md) Enforces that all connected components are defined in a separate file.
36 changes: 36 additions & 0 deletions docs/rules/mapStateToProps-no-store.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Enforces that mapStateToProps does not bind complete store to a component. (react-redux/mapStateToProps-no-store)

Passing whole state to a component is a bd practice. Triggering unnecessary re-renders.
Instead one should provide specific properties used by a component.

## Rule details

The following patterns are considered incorrect:

```js
const mapStateToProps = (state) => state
```

```js
const mapStateToProps = state => {
return {state: state}
}
```

```js
connect((state) => state, null)(App)
```

The following patterns are correct:

```js
const mapStateToProps = () => {}
```

```js
const mapStateToProps = (state) => {isActive: state.isActive}
```

```js
connect((state) => ({isActive: state.isActive}), null)(App)
```
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const rules = {
'connect-prefer-named-arguments': require('./lib/rules/connect-prefer-named-arguments'),
'mapDispatchToProps-prefer-object': require('./lib/rules/mapDispatchToProps-prefer-object'),
'mapDispatchToProps-prefer-parameters-names': require('./lib/rules/mapDispatchToProps-prefer-parameters-names'),
'mapStateToProps-no-store': require('./lib/rules/mapStateToProps-no-store'),
'mapStateToProps-prefer-parameters-names': require('./lib/rules/mapStateToProps-prefer-parameters-names'),
'prefer-separate-component-file': require('./lib/rules/prefer-separate-component-file'),
};
Expand All @@ -27,6 +28,7 @@ module.exports = {
'react-redux/connect-prefer-named-arguments': 2,
'react-redux/mapDispatchToProps-prefer-parameters-names': 2,
'react-redux/mapDispatchToProps-prefer-object': 2,
'react-redux/mapStateToProps-no-store': 2,
'react-redux/mapStateToProps-prefer-parameters-names': 2,
'react-redux/prefer-separate-component-file': 1,
},
Expand Down
51 changes: 51 additions & 0 deletions lib/rules/mapStateToProps-no-store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const utils = require('../utils');
const isReactReduxConnect = require('../isReactReduxConnect');

const report = function (context, node) {
context.report({
message: 'mapStateToProps should not return complete store object',
node,
});
};

const getFirstParamName = node =>
node.params && node.params[0] && node.params[0].name;

const checkFunction = function (context, body, firstParamName) {
const returnNode = utils.getReturnNode(body);
// return state;
if (returnNode && returnNode.type === 'Identifier' && returnNode.name === firstParamName) {
report(context, body);
}
// return {store: state};
if (returnNode && returnNode.type === 'ObjectExpression' &&
returnNode.properties.reduce((acc, cv) =>
acc || (cv.value.name === firstParamName), false)) {
report(context, body);
}
};

module.exports = function (context) {
return {
VariableDeclaration(node) {
node.declarations.forEach((decl) => {
if (decl.id && decl.id.name === 'mapStateToProps') {
const body = decl.init.body;
const firxtParamName = decl.init.params &&
decl.init.params[0] &&
decl.init.params[0].name;
checkFunction(context, body, firxtParamName);
}
});
},
FunctionDeclaration(node) {
checkFunction(context, node.body, getFirstParamName(node));
},
CallExpression(node) {
if (isReactReduxConnect(node)) {
const mapStateToProps = node.arguments && node.arguments[0];
checkFunction(context, mapStateToProps.body, getFirstParamName(mapStateToProps));
}
},
};
};
18 changes: 18 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

const getReturnNode = (node) => {
const body = node.body;
if (!body || !body.length) {
return node;
}
for (let i = body.length - 1; i >= 0; i -= 1) {
if (body[i].type === 'ReturnStatement') {
return body[i].argument;
}
}
return null;
};

module.exports = {
getReturnNode,
};
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"scripts": {
"lint": "eslint ./",
"test": "npm run lint && mocha tests --recursive",
"test-single": "mocha tests/lib/rules/prefer-separate-component-file",
"debug-test": "mocha --debug-brk --inspect tests/index.js",
"test-single": "mocha tests/lib/rules/mapStateToProps-no-store",
"debug-test": "mocha --debug-brk --inspect tests/lib/rules/mapStateToProps-no-store",
"semantic-release": "semantic-release",
"commitmsg": "npm run test && commitlint -e $GIT_PARAMS"
},
Expand Down
96 changes: 96 additions & 0 deletions tests/lib/rules/mapStateToProps-no-store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
require('babel-eslint');

const rule = require('../../../lib/rules/mapStateToProps-no-store');
const RuleTester = require('eslint').RuleTester;

const parserOptions = {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: {
experimentalObjectRestSpread: true,
},
};

const ruleTester = new RuleTester({ parserOptions });

ruleTester.run('mapStateToProps-no-store', rule, {
valid: [
'connect((state) => ({isActive: state.isActive}), null)(App)',
`connect(
(state) => {
return {
isActive: state.isActive
}
},
null
)(App)
`,
`connect(function(state){
return {
isActive: state.isActive
}
},
null
)(App)
`,
`function mapStateToProps(state) {
return {};
}`,
`const mapStateToProps = function(state) {
return state.isActive;
}`,
'const mapStateToProps = (state, ownProps) => {}',
'const mapStateToProps = (state) => {isActive: state.isActive}',
],
invalid: [{
code: 'const mapStateToProps = (state) => state',
errors: [
{
message: 'mapStateToProps should not return complete store object',
},
],
}, {
code: `const mapStateToProps = state => {
return {state: state}
}`,
errors: [
{
message: 'mapStateToProps should not return complete store object',
},
],
}, {
code: `function mapStateToProps(state) {
return state;
}`,
errors: [
{
message: 'mapStateToProps should not return complete store object',
},
],
}, {
code: `export default connect(
(state) => {
return {
state: state
}
},
(dispatch) => {
return {
actions: bindActionCreators(actions, dispatch)
}
}
)(App)`,
errors: [
{
message: 'mapStateToProps should not return complete store object',
},
],
}, {
code: 'connect((state) => state, null)(App)',
errors: [
{
message: 'mapStateToProps should not return complete store object',
},
],
}],
});

0 comments on commit caa90bb

Please sign in to comment.