Skip to content

Commit 19faaac

Browse files
committed
Add rule for disallowing .bind in JSX props. Closes #184
1 parent 832a153 commit 19faaac

File tree

4 files changed

+209
-0
lines changed

4 files changed

+209
-0
lines changed

docs/rules/jsx-no-bind.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# No `.bind()` or arrow functions in JSX props
2+
3+
A `bind` call or [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) in a JSX prop will create a brand new function on every single render. This is bad for performance, as it will result in the garbage collector being invoked way more than is necessary.
4+
5+
## Rule Details
6+
7+
The following patterns are considered warnings:
8+
9+
```js
10+
<div onClick={this._handleClick.bind(this)}></div>
11+
```
12+
```js
13+
<div onClick={() => console.log('Hello!'))}></div>
14+
```
15+
16+
The following patterns are not considered warnings:
17+
```js
18+
<div onClick={this._handleClick}></div>
19+
```
20+
21+
## Protip
22+
23+
A common use case of `bind` in render is when rendering a list, to have a separate callback per list item:
24+
25+
```js
26+
var List = React.createClass({
27+
render() {
28+
return (
29+
<ul>
30+
{this.props.items.map(item =>
31+
<li key={item.id} onClick={this.props.onItemClick.bind(null, item.id)}>
32+
...
33+
</li>
34+
)}
35+
</ul>
36+
);
37+
}
38+
});
39+
```
40+
41+
Rather than doing it this way, pull the repeated section into its own component:
42+
43+
```js
44+
var List = React.createClass({
45+
render() {
46+
return (
47+
<ul>
48+
{this.props.items.map(item =>
49+
<ListItem key={item.id} item={item} onItemClick={this.props.onItemClick} />
50+
)}
51+
</ul>
52+
);
53+
}
54+
});
55+
56+
var ListItem = React.createClass({
57+
render() {
58+
return (
59+
<li onClick={this._onClick}>
60+
...
61+
</li>
62+
);
63+
},
64+
_onClick() {
65+
this.props.onItemClick(this.props.item.id);
66+
}
67+
});
68+
```
69+
70+
This will speed up rendering, as it avoids the need to create new functions (through `bind` calls) on every render.
71+
72+
## When Not To Use It
73+
74+
If you do not want to enforce that `bind` or arrow functions are not used in props, then you can disable this rule.

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = {
1414
'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
1515
'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
1616
'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
17+
'jsx-no-bind': require('./lib/rules/jsx-no-bind'),
1718
'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
1819
'jsx-quotes': require('./lib/rules/jsx-quotes'),
1920
'no-unknown-property': require('./lib/rules/no-unknown-property'),
@@ -45,6 +46,7 @@ module.exports = {
4546
'no-did-update-set-state': 0,
4647
'react-in-jsx-scope': 0,
4748
'jsx-uses-vars': 1,
49+
'jsx-no-bind': 0,
4850
'jsx-no-undef': 0,
4951
'jsx-quotes': 0,
5052
'no-unknown-property': 0,

lib/rules/jsx-no-bind.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @fileoverview Prevents usage of Function.prototype.bind and arrow functions
3+
* in React component definition.
4+
* @author Daniel Lo Nigro <dan.cx>
5+
*/
6+
'use strict';
7+
8+
// -----------------------------------------------------------------------------
9+
// Rule Definition
10+
// -----------------------------------------------------------------------------
11+
12+
module.exports = function(context) {
13+
var configuration = context.options[0] || {};
14+
15+
return {
16+
JSXAttribute: function(node) {
17+
if (!node.value || !node.value.expression) {
18+
return;
19+
}
20+
var valueNode = node.value.expression;
21+
if (
22+
!configuration.allowBind &&
23+
valueNode.type === 'CallExpression' &&
24+
valueNode.callee.property.name === 'bind'
25+
) {
26+
context.report(node, 'JSX props should not use .bind()');
27+
} else if (
28+
!configuration.allowArrowFunctions &&
29+
valueNode.type === 'ArrowFunctionExpression'
30+
) {
31+
context.report(node, 'JSX props should not use arrow functions');
32+
}
33+
}
34+
};
35+
};
36+
37+
module.exports.schema = [{
38+
type: 'object',
39+
properties: {
40+
allowArrowFunctions: {
41+
default: false,
42+
type: 'boolean'
43+
},
44+
allowBind: {
45+
default: false,
46+
type: 'boolean'
47+
}
48+
},
49+
additionalProperties: false
50+
}];

tests/lib/rules/jsx-no-bind.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* @fileoverview Prevents usage of Function.prototype.bind and arrow functions
3+
* in React component definition.
4+
* @author Daniel Lo Nigro <dan.cx>
5+
*/
6+
'use strict';
7+
8+
// -----------------------------------------------------------------------------
9+
// Requirements
10+
// -----------------------------------------------------------------------------
11+
12+
var rule = require('../../../lib/rules/jsx-no-bind');
13+
var RuleTester = require('eslint').RuleTester;
14+
15+
// -----------------------------------------------------------------------------
16+
// Tests
17+
// -----------------------------------------------------------------------------
18+
19+
var ruleTester = new RuleTester();
20+
ruleTester.run('jsx-no-bind', rule, {
21+
22+
valid: [
23+
// Not covered by the rule
24+
{
25+
code: '<div onClick={this._handleClick}></div>',
26+
parser: 'babel-eslint'
27+
},
28+
{
29+
code: '<div meaningOfLife={42}></div>',
30+
parser: 'babel-eslint'
31+
},
32+
33+
// bind() explicitly allowed
34+
{
35+
code: '<div onClick={this._handleClick.bind(this)}></div>',
36+
options: [{allowBind: true}],
37+
parser: 'babel-eslint'
38+
},
39+
40+
// Arrow functions explicitly allowed
41+
{
42+
code: '<div onClick={() => alert("1337")}></div>',
43+
options: [{allowArrowFunctions: true}],
44+
parser: 'babel-eslint'
45+
}
46+
],
47+
48+
invalid: [
49+
// .bind()
50+
{
51+
code: '<div onClick={this._handleClick.bind(this)}></div>',
52+
errors: [{message: 'JSX props should not use .bind()'}],
53+
parser: 'babel-eslint'
54+
},
55+
{
56+
code: '<div onClick={someGlobalFunction.bind(this)}></div>',
57+
errors: [{message: 'JSX props should not use .bind()'}],
58+
parser: 'babel-eslint'
59+
},
60+
{
61+
code: '<div onClick={window.lol.bind(this)}></div>',
62+
errors: [{message: 'JSX props should not use .bind()'}],
63+
parser: 'babel-eslint'
64+
},
65+
66+
// Arrow functions
67+
{
68+
code: '<div onClick={() => alert("1337")}></div>',
69+
errors: [{message: 'JSX props should not use arrow functions'}],
70+
parser: 'babel-eslint'
71+
},
72+
{
73+
code: '<div onClick={() => 42}></div>',
74+
errors: [{message: 'JSX props should not use arrow functions'}],
75+
parser: 'babel-eslint'
76+
},
77+
{
78+
code: '<div onClick={param => { first(); second(); }}></div>',
79+
errors: [{message: 'JSX props should not use arrow functions'}],
80+
parser: 'babel-eslint'
81+
}
82+
]
83+
});

0 commit comments

Comments
 (0)