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.

Tweet This Interesting post by @BenNadel - You Don't Need To Use .bind( this ) When Using .forEach() Or .map() In ReactJS Woot woot — you rock the party that rocks the body!







