Skip to content

Commit 8ac35f5

Browse files
committed
Merge pull request #274 from Daniel15/jsx-no-bind
Add jsx-no-bind rule (fixes #184)
2 parents 832a153 + 719aebe commit 8ac35f5

File tree

5 files changed

+238
-0
lines changed

5 files changed

+238
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Finally, enable all of the rules that you would like to use.
5353
"react/jsx-curly-spacing": 1,
5454
"react/jsx-indent-props": 1,
5555
"react/jsx-max-props-per-line": 1,
56+
"react/jsx-no-bind": 1,
5657
"react/jsx-no-duplicate-props": 1,
5758
"react/jsx-no-literals": 1,
5859
"react/jsx-no-undef": 1,
@@ -88,6 +89,7 @@ Finally, enable all of the rules that you would like to use.
8889
* [jsx-curly-spacing](docs/rules/jsx-curly-spacing.md): Enforce or disallow spaces inside of curly braces in JSX attributes
8990
* [jsx-indent-props](docs/rules/jsx-indent-props.md): Validate props indentation in JSX
9091
* [jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md): Limit maximum of props on a single line in JSX
92+
* [jsx-no-bind](docs/rules/jsx-no-bind.md): Prevent usage of `.bind()` and arrow functions in JSX props
9193
* [jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md): Prevent duplicate props in JSX
9294
* [jsx-no-literals](docs/rules/jsx-no-literals.md): Prevent usage of unwrapped JSX strings
9395
* [jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX

docs/rules/jsx-no-bind.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
## Protips
22+
23+
### Lists of Items
24+
25+
A common use case of `bind` in render is when rendering a list, to have a separate callback per list item:
26+
27+
```js
28+
var List = React.createClass({
29+
render() {
30+
return (
31+
<ul>
32+
{this.props.items.map(item =>
33+
<li key={item.id} onClick={this.props.onItemClick.bind(null, item.id)}>
34+
...
35+
</li>
36+
)}
37+
</ul>
38+
);
39+
}
40+
});
41+
```
42+
43+
Rather than doing it this way, pull the repeated section into its own component:
44+
45+
```js
46+
var List = React.createClass({
47+
render() {
48+
return (
49+
<ul>
50+
{this.props.items.map(item =>
51+
<ListItem key={item.id} item={item} onItemClick={this.props.onItemClick} />
52+
)}
53+
</ul>
54+
);
55+
}
56+
});
57+
58+
var ListItem = React.createClass({
59+
render() {
60+
return (
61+
<li onClick={this._onClick}>
62+
...
63+
</li>
64+
);
65+
},
66+
_onClick() {
67+
this.props.onItemClick(this.props.item.id);
68+
}
69+
});
70+
```
71+
72+
This will speed up rendering, as it avoids the need to create new functions (through `bind` calls) on every render.
73+
74+
### ES6 Classes
75+
76+
Unfortunately [React ES6 classes](https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#es6-classes) do not autobind their methods like components created with the older `React.createClass` syntax. There are several approaches to binding methods for ES6 classes. A basic approach is to just manually bind the methods in the constructor:
77+
78+
```js
79+
class Foo extends React.Component {
80+
constructor() {
81+
super();
82+
this._onClick = this._onClick.bind(this);
83+
}
84+
render() {
85+
return (
86+
<div onClick={this._onClick}>
87+
Hello!
88+
</div>
89+
);
90+
}
91+
_onClick() {
92+
// Do whatever you like, referencing "this" as appropriate
93+
}
94+
}
95+
```
96+
97+
A more sophisticated approach would be to use something like an [autobind ES7 decorator](https://www.npmjs.com/package/core-decorators#autobind) or [property initializers](https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding).
98+
99+
## When Not To Use It
100+
101+
If you do not use JSX or 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)