Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Enable the rules that you would like to use.
* [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
* [react/forbid-foreign-prop-types](docs/rules/forbid-foreign-prop-types.md): Forbid foreign propTypes
* [react/no-access-state-in-setstate](docs/rules/no-access-state-in-setstate.md): Prevent using this.state inside this.setState
* [react/no-allocation-in-props](docs/rules/no-allocation-in-props.md): Prevent usage of `[]` and `{}` in props
* [react/no-array-index-key](docs/rules/no-array-index-key.md): Prevent using Array index in `key` props
* [react/no-children-prop](docs/rules/no-children-prop.md): Prevent passing children as props
* [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
Expand Down
34 changes: 34 additions & 0 deletions docs/rules/no-allocation-in-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# No Array or Object Allocation in Props (react/no-allocation-in-props)

An array literal `[]` or an object literal `{}` in a prop will create a brand new object on every single render. This is bad for performance, as it will result in the garbage collector being invoked way more than is necessary. It may also cause unnecessary re-renders if a brand new object is passed as a prop to a component that uses reference equality check on the prop to determine if it should update.

## Rule Details

The following patterns are considered warnings:

```jsx
<Foo bar={[1, 2, 3]}></Foo>
```
```jsx
<Foo bar={{ foo: 1 }}></Foo>
```

## Rule Options

```js
"react/no-allocation-in-props": [<enabled>, {
"ignoreDOMComponents": <boolean> || false
}]
```

### `ignoreDOMComponents`

When `true` the following is **not** considered a warning:

```jsx
<div style={{ foo: 1 }} />
```

## When Not To Use It

If you do not want to enforce that array/object allocations don't occur in props, then you can disable this rule.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const allRules = {
'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
'jsx-wrap-multilines': require('./lib/rules/jsx-wrap-multilines'),
'no-access-state-in-setstate': require('./lib/rules/no-access-state-in-setstate'),
'no-allocation-in-props': require('./lib/rules/no-allocation-in-props'),
'no-array-index-key': require('./lib/rules/no-array-index-key'),
'no-children-prop': require('./lib/rules/no-children-prop'),
'no-danger': require('./lib/rules/no-danger'),
Expand Down
196 changes: 196 additions & 0 deletions lib/rules/no-allocation-in-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/**
* @fileoverview Disallow array and object literals as props values.
* @author alexzherdev
*/
'use strict';

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

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

const violationMessageStore = {
array: 'Props should not use array allocations',
object: 'Props should not use object allocations'
};

module.exports = {
meta: {
docs: {
description: 'Disallow array and object literals as props values.',
category: 'Best Practices',
recommended: false,
url: docsUrl('no-allocation-in-props')
},

schema: [{
type: 'object',
properties: {
allowArrays: {
type: 'boolean',
default: false
},
allowObjects: {
type: 'boolean',
default: false
},
ignoreDOMComponents: {
type: 'boolean',
default: false
}
},
additionalProperties: false
}]
},

create: Components.detect((context, components, utils) => {
const configuration = context.options[0] || {};

// Keep track of all the variable names pointing to a bind call,
// bind expression or an arrow function in different block statements
const blockVariableNameSets = {};

function setBlockVariableNameSet(blockStart) {
blockVariableNameSets[blockStart] = {
array: new Set(),
object: new Set()
};
}

function getNodeViolationType(node) {
const nodeType = node.type;

if (
nodeType === 'ConditionalExpression'
) {
return getNodeViolationType(node.test) ||
getNodeViolationType(node.consequent) ||
getNodeViolationType(node.alternate);
} else if (
!configuration.allowArrays &&
nodeType === 'ArrayExpression'
) {
return 'array';
} else if (
!configuration.allowObjects &&
nodeType === 'ObjectExpression'
) {
return 'object';
}

return null;
}

function addVariableNameToSet(violationType, variableName, blockStart) {
blockVariableNameSets[blockStart][violationType].add(variableName);
}

function getBlockStatementAncestors(node) {
return context.getAncestors(node).reverse().filter(
ancestor => ancestor.type === 'BlockStatement'
);
}

function reportVariableViolation(node, name, blockStart) {
const blockSets = blockVariableNameSets[blockStart];
const violationTypes = Object.keys(blockSets);

return violationTypes.find(type => {
if (blockSets[type].has(name)) {
context.report({node: node, message: violationMessageStore[type]});
return true;
}

return false;
});
}

function findVariableViolation(node, name) {
getBlockStatementAncestors(node).find(
block => reportVariableViolation(node, name, block.start)
);
}

// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------

return {
BlockStatement(node) {
setBlockVariableNameSet(node.start);
},

VariableDeclarator(node) {
if (!node.init) {
return;
}
const blockAncestors = getBlockStatementAncestors(node);
const variableViolationType = getNodeViolationType(node.init);

if (
blockAncestors.length > 0 &&
variableViolationType &&
node.parent.kind === 'const' // only support const right now
) {
addVariableNameToSet(
variableViolationType, node.id.name, blockAncestors[0].start
);
}
},

JSXAttribute(node) {
const isDOMComponent = jsxUtil.isDOMComponent(node.parent);
if (configuration.ignoreDOMComponents && isDOMComponent) {
return;
}

const valueNode = node.value.expression;
const valueNodeType = valueNode.type;

const nodeViolationType = getNodeViolationType(valueNode);

if (valueNodeType === 'Identifier') {
findVariableViolation(node, valueNode.name);
} else if (nodeViolationType) {
context.report({
node: node, message: violationMessageStore[nodeViolationType]
});
}
},

CallExpression(node) {
if (!utils.isReactCreateElement(node) || node.arguments.length === 0) {
return;
}

const isDOMComponent = node.arguments[0].type === 'Literal';

if (configuration.ignoreDOMComponents && isDOMComponent) {
return;
}

const propsArg = node.arguments[1];
if (!propsArg || propsArg.type !== 'ObjectExpression') {
return;
}

propsArg.properties.forEach(prop => {
if (prop.value) {
const nodeViolationType = getNodeViolationType(prop.value);

if (prop.value.type === 'Identifier') {
findVariableViolation(node, prop.value.name);
} else if (nodeViolationType) {
context.report({
node: prop, message: violationMessageStore[nodeViolationType]
});
}
}
});
}
};
})
};
Loading