You Don't Need To Use .bind( this ) When Using .forEach() Or .map() In ReactJS
Often times, in a ReactJS Element (Component), you need to map a data collection onto an Element collection for rendering. The ReactJS community seems to have settled on using the ES5 methods .forEach() and .map() to fulfill these kinds of tasks. But, unfortunately, I'm seeing people using the .bind() method to ensure that the iterators execute in the correct context. If we're going to be using ES5 iteration methods, we don't need to do this - these methods already accept an optional parameter for context binding.
Run this demo in my JavaScript Demos project on GitHub.
Out of the box, ReactJS already gives us some context-binding magic. When an Element (Component) is instantiated in ReactJS, the constructor function auto-binds the class specification methods to the instance of the element. This way, whenever you invoke those methods, they are invoked in the context of the element, even if they're invoked as naked function references. As such, we never need to use .bind() when passing instance methods to child elements as props.
Since iteration methods like .forEach(), .map(), and .filter() are typically defined inside the render() method, ReactJS doesn't have the opportunity to auto-bind these for us instantiation. However, if you look at the signatures for these methods, they all accept an optional second argument, which is the context (ie, the [this] binding) in which to execute the iterator. These methods already provide the functionality that some people are trying to recreate with .bind().
If you are not sure what I am talking about, there are a number of demos (including demos in the ReactJS documentation) that are performing iteration like this:
[].forEach( function(){ ..... }.bind( this ) );
Notice the .bind() method call being used to bind the iterator to the Element instance context. This is unnecessary.
To see the native context-binding functionality for iteration methods, I've put together a tiny ReactJS demo that has to map a collection of friends onto a collection of Friend elements. To do this, I'm using the .map() method and I'm making sure to pass-in "this" as the context. This allows me to use the "natural" context for the Element.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>
You Don't Need To Use .bind( this ) When Using .forEach() Or .map() In ReactJS
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body>
<h1>
You Don't Need To Use .bind( this ) When Using .forEach() Or .map() In ReactJS
</h1>
<div id="content">
<!-- This content will be replaced with the React rendering. -->
</div>
<!-- Load scripts. -->
<script src="../../vendor/reactjs/react-0.13.3.min.js"></script>
<script src="../../vendor/reactjs/JSXTransformer-0.13.3.js"></script>
<script type="text/jsx">
// I manage the Demo widget.
var Demo = React.createClass({
// I provide the initial view-model, before the component is mounted.
getInitialState: function() {
return({
friends: [
{
id: 1,
name: "Sarah"
},
{
id: 2,
name: "Joanna"
},
{
id: 3,
name: "Kim"
}
],
selection: []
});
},
// ---
// PUBLIC METHODS.
// ---
// I render the view using the current state and properties collections.
render: function() {
// Map friends to Friend elements.
// --
// NOTE: As part of the .map() invocation, I'm explicitly defining
// [this] as the context in the second argument. Doing so will cause
// the iterator to be executed in the Element context which means that
// I can continue to use "this." in a natural, expected fashion.
var friends = this.state.friends.map(
function iterator( friend ) {
return(
<Friend
key={ friend.id }
friend={ friend }
isSelected={ this.isSelected( friend ) }
toggleSelection={ this.toggleSelection }>
</Friend>
);
},
this
);
return(
<div>
<h2>
Friends
</h2>
<ul>
{ friends }
</ul>
</div>
);
},
// I toggle the selection of the given friend.
toggleSelection: function( friend ) {
var index = this.state.selection.indexOf( friend.id );
var newSelection = this.state.selection.slice();
// Friend is not currently selected.
if ( index === -1 ) {
newSelection.push( friend.id );
// Friend is currently selected.
} else {
newSelection.splice( index, 1 );
}
this.setState({
selection: newSelection
});
},
// ---
// PRIVATE METHODS.
// ---
// I determine if the given friend is currently selected.
isSelected: function( friend ) {
return( this.state.selection.indexOf( friend.id ) !== -1 );
}
});
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I manage the individual friend in the list.
var Friend = React.createClass({
// I handle the click events on the Friend.
handleClick: function( event ) {
// Pass the mutation request for selection up to the parent.
this.props.toggleSelection( this.props.friend );
},
// I render the friend.
render: function() {
// If the friend is currently selected, Bold the output.
if ( this.props.isSelected ) {
return(
<li onClick={ this.handleClick }>
<strong>{ this.props.friend.name }</strong>
{ " " }
— woot!
</li>
);
} else {
return(
<li onClick={ this.handleClick }>
{ this.props.friend.name }
</li>
);
}
}
});
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// Render the root Demo and mount it inside the given element.
React.render( <Demo />, document.getElementById( "content" ) );
</script>
</body>
</html>
As you can see, my call to .map() takes both the iterator and the [this] reference. Doing this allows me to reference this.isSelected() and this.toggleSelection in a natural manner even though the code is inside of a function that is being passed out of scope.
NOTE: Function.prototype.bind and Array.prototype.[forEach,map,filter] were all added in Internet Explorer 9 (IE9). So, if you're going to buy into support for .bind(), you're implicitly buying into support for the iterator signatures with the optional [this] argument.
Since I've never really taken advantage of native iteration methods - I usually use lodash.js - I wanted to run a quick sanity check to make sure that this actually works in all the modern browsers, plus IE9. To do so, I put together a quick test case:
NOTE: lodash.js also allows for optional [this] context binding in many of its methods.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>
Browser Test For Collection Functions
</title>
</head>
<body>
<h1>
Browser Test For Collection Functions
</h1>
<!-- Load scripts. -->
<script type="text/javascript">
var context = {
hello: "world"
};
// Testing to make sure that various Array.prototype methods support the
// context / thisArg argument to ensure proper [this] references.
[ "hello" ].forEach(
function iterator( item ) {
console.log( "forEach:", item, this[ item ] );
},
context
);
[ "hello" ].map(
function iterator( item ) {
console.log( "map:", item, this[ item ] );
},
context
);
[ "hello" ].filter(
function iterator( item ) {
console.log( "filter:", item, this[ item ] );
},
context
);
</script>
</body>
</html>
This just makes sure that the iterator methods always executes as if bound to the "context" object. And, when I run this is in all the browsers, including IE9, I get the following console output:
forEach: hello world
map: hello world
filter: hello world
As you can see, all is good. If you're going to be using .forEach() or .map() in your ReactJS applications, you're making an implicit statement about browser support. And, if you're already doing that, you might as well use the optional [this] argument for context binding - there's no need to use the Function.prototype.bind() method.
Want to use code from this post? Check out the license.
Reader Comments
It's common practice to use ES6 arrow functions for iteration, which automatically get the same "this" as their containing scope and a have shorthand form which allows you to omit "return" if the body of the function is a single statement:
https://gist.github.com/insin/1501f30374d00ee3f4e3
Both the JSX Transformer and Babel ( which JSX Transformer is being replaced with - see https://facebook.github.io/react/blog/2015/06/12/deprecating-jstransform-and-react-tools.html ) support transforming arrow functions to ES5, but you need to use the harmony flag to enable it in the former:
<script type="text/jsx;harmony=true">
React is a gateway drug to transpilers, as you need to integrate one to use JSX. Once you're doing so, there's less of a barrier to using ES6 features, since the transpiler is already right there integrated into your workflow. It's a small step to start using conveniences like arrow functions, compact method definitions, object literal shorthand, destructuring, string interpolation etc.
@Jonny,
Awesome tip - I didn't realize that was a feature of JSX. And to be honest, the JSX transpiler is really the first one that I've used. But, with the growing popularity of ES6, it will something that I probably have to look into soon. For the most part, I don't feel much friction when it comes to core JavaScript, so I haven't had much desire to dive into ES6 yet. But, it's on my list of things to do.
@Jonny,
That said, I've never been a fan of implicit "return" values. That's probably a feature that I will never embrace. Even when I've tried languages that support it natively, I always opt for a return() statement. I think it reduces the cognitive load of the code.
Also, you don't need to call `.bind(this)` with `.forEach` & `.map` since most--if not all--of the Array methods optionally take a `thisArg` as their last argument.
forEach: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
map: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
@Fesh,
Ah, awesome - thanks for linking those!
Hey Ben,
you even do not need to bind store callbacks or onClick handlers and the lot, if you use React.createClass. The framework will take care of it. If you convert to using ES6 Classes, you need to bind the callbacks to the current class.
@David,
That's really good to know. I have not yet messed with any of the transpiler stuff yet. Right now, my only experience is with the in-browser JSX Trasformer. I also, admittedly, don't know all that much about ES6 yet. Do you think that ReactJS omits the auto-binding in an ES6 context because it is expecting people to use the so-called "Fat arrow" notation to perform the auto-binding?
@Jonny,
Wow! I have been digging React over the past month+. I have been stuffing "this" into a local "self" var. Support for ES6 arrow function looks awesome. Time to overhaul my own code. :)
Timely suggestions , I am thankful for the details , Does anyone know where I could possibly find a fillable IRS 1120-RIC version to fill out ?