React & Autobinding
JavaScript's `this` keyword is one that creates a lot of controversy in the community. It is your best friend as well as your worst enemy. Unlike with class-based programming languages, a developer writing JavaScript code doesn't always know what `this` is referring to. It may even get more complicated when scoped functions, callbacks and promises are introduced, where the nature of `this` is sometimes unknown.
As an avid JavaScript developer myself, I have the pleasure of dealing with `this` on a daily basis. Most of the time I am able to handle the hardships that come with such a loyal friendship, however there are situations where I wish `this` would explicitly yell its value out loud at me for reassurance.
React & `this`
Whether you're building a React Web application, a React Native iOS/Android app, or a React Native Ubuntu application, React has proved very developer friendly and a wonderful library to work with. That being said, it automatically comes encompassed with our best friend `this`. React uses JavaScript's `this` to refer to the context inside of each component class. That is why you are able to run code like this:
this.setState({ someKey: someValue })
The code above uses the `this` keyword to reference the current component's context and in turn runs the setState method on that component class. This seems like an expected and straight forward behavior.
The real problem starts to arise when we have something like this in our component code:
this.setState({ dataFetched: false })
fetch('/data').then(function fetchComplete() {
this.setState({ dataFetched: true })
})
Do you see the issue with the code snippet above? The first statement will run without a hitch. However the this.setState() in the fourth line will return a TypeError. The reason for that is because this.setState is not a function, since `this` no longer refers to the component class. As you can see, inside of the then portion of the fetch promise, we run another function called fetchComplete. Because functions in JavaScript are scoped , `this` inside of the fetchComplete function is no longer a reference to the component class that holds the state.
React Methods
At this point you are probably already writing ES6 JavaScript (ES2015) instead of ES5. If not, you should really check it out! Along with many other new features, Es6 brought to JavaScript, and subsequently to React, the `class` keyword. With that came the ability to make component declarations using a more intuitive OOP approach. Where in the past you would write:
var MyComponent = React.createClass({
myMethodName: function() {
/* ... */
}, render: function() {
return (
<div>
<h1>Hello World</h1>
</div>
)
}
})
In ES6 you can now write:
class MyComponent extends React.Component {
myMethodName() {
/* ... */
} render() {
return (
<div>
<h1>Hello World</h1>
</div>
)
}
}
While in Es5 your component was an object being passed to the createClass() function, in the ES6 version it is an extension of a React Component class. One of the changes that most developers that use the class-based approach are now familiar with is the annoying binding of `this`. With the createClass() approach, any methods that are written inside of that component automatically bind `this` to the context of the component. In the class-based approach the methods are simply JavaScript functions, and therefore are scoped themselves. Due to this change, you might now be familiar with this sort of code in your component's constructors:
class MyComponent extends React.Component {
constructor() {
/* ... */
this.addTask = this.addTask.bind(this)
this.completeTask = this.completeTask.bind(this)
} addTask(task) {
/* ... */
this.setState({ task })
} completeTask(taskId) {
/* ... */
this.setState({ tasksComplete: taskId })
} render() {
return (
/* ... */
)
}
}
All methods that need to have a reference to `this` will need to be manually binded. The constructor code above is a way to do just that. It binds `this` to the correct reference of the component's context. This again seems like an expected and straight forward resolution. However, things start to go south when you have 10–15+ methods in some components. You're looking at something like this:
this.addTask = this.addTask.bind(this)
this.removeTask = this.removeTask.bind(this)
this.fetchTasks = this.fetchTasks.bind(this)
this.starTask = this.starTask.bind(this)
this.activateTask = this.activateTask.bind(this)
this.inactivateTask = this.activateTask.bind(this)
this.relateTasks = this.relateTasks.bind(this)
/* ... */
On top of being ugly code, this is a repetitious task and as a programmer you should be able to make this code more efficient. An initial thought might be to create an array with all the method names and loop through that array binding each individual method to `this`. Although that would technically work, there are yet more efficient ways to solve this binding issue.
Possible Solution: Autobinding Packages
The JavaScript community has seen extreme growth in the number of open source modules, libraries and components released to NPM. Because of that, it almost seems second nature to want to search for `autobinding`, `react-autobinding`, `react-bind-methods-babel-preset`, `autobind-my-methods-automatically-left-pad`, or any other autobinding-related name you can come up with. And guess what, you're not wrong to assume so.
I'm sure there are multiple other implementations of these same tools but I figured I'd touch on two of the best ones out there.
1. Autobind Decorator
Autobind Decorator is an NPM package which binds methods of a class to the correct instance of `this`, even when the methods are detached. Not many people are aware of this, but JavaScript has gotten a lot of goodies from Python - think Iterators, Generators and Array Comprehensions. Like the name denotes, the Autobind Decorator package borrows from the (Python) pattern of Decorators, which is used to run a function that takes another function, extending the behavior of the latter function without explicitly modifying it.
The package uses `@autobind` before methods to bind `this` to the correct reference to the component's context.
import autobind from 'autobind-decorator'class MyComponent extends React.Component {
constructor() {
/* ... */
} @autobind
addTask(task) {
/* ... */
this.setState({ task });
} @autobind
myMethod2(task) {
/* ... */
this._anotherBindedMethod(task);
} render() {
return (
/* ... */
)
}
}
This seems like a simple solution, but I'd rather not have to add a line above each individual method inside each of my React components. Not to worry, Autobind Decorator is smart enough to let us bind all methods inside a component class at once. Like so:
import autobind from 'autobind-decorator'@autobind
class MyComponent extends React.Component {
constructor() {
/* ... */
} addTask(task) {
/* ... */
this.setState({ task });
} /* ... */}
And just like that, `this` issue is resolved.
2. Class Autobind
Class Autobind is another NPM package that is widely used to solve this binding issue. Unlike Autobind Decorator, it does not use of the decorator pattern, but really just uses a function inside your constructor that automatically binds the Component's methods to the correct reference of `this`.
import autobind from 'class-autobind';class MyComponent extends React.Component {
constructor() {
autobind(this);
} addTask(task) {
/* ... */
this.setState({ task });
} /* ... */
}
Simple isn't it? However if instead of binding all of your methods at once, you would rather choose to bind only select methods, React Autobind has got you covered. React Autobind is yet another NPM package that uses the same approach as Class Autobind, but it allows you to pass additional parameters to the `autoBind` function, like so:
import autoBind from 'react-autobind'class MyComponent extends React.Component {
constructor() {
autoBind(this, 'addTask', 'secondMethod', 'thirdMethod')
} addTask(task) {
/* ... */
this.setState({ task })
} /* ... */
}
Best Solution: ES2015 Arrow Functions
ECMAScript 2015 finally brought along many sought-out features to JavaScript. One of my favorite and most used new features are the arrow functions. ES2015 introduces the arrow function syntax, which allows developers to write function expressions. Informally called `fat arrow functions`, arrow function expressions are usually terser than regular function declarations, have implicit returns and always use the value of `this` from the enclosing scope. Simply put, arrow functions lexically bind the `this` value so you don't have to worry about it.
class MyComponent extends React.Component {
constructor() {
/* ... */
} addTask = (task) => {
/* ... */
this.setState({ task })
} myMethod2 = (task) => {
/* ... */
this._anotherBindedMethod(task)
} /* ... */
}
This code is much cleaner and any developer is able to reference `this` to the context of the React component. I'm a big fan of arrow function expressions as they can radically transform your codebase and make it more readable and maintainable.
Bonus Solution: ES2016 Bind Syntax
ES2016 is coming soon and with tools like Babel to transpile our ESNext code, we should all feel quite comfortable using the latest features in the language. It may be that not all of the latest features are of use for your application or specific use case, but there is usually a really good reason why a feature has made it into the specification.
Although React component binding isn't the greatest use case for the new ES2016 Bind Syntax, I wanted to touch on it because it will make the handling of `this` even easier.
The Bind Syntax proposal introduced us to a new operator :: which performs `this` binding and method extraction. The operator is quite simple to use, it expects a value on the left and a function on the right.
[3, 4, 5]::map(x => x * 2)
// [6, 8, 10]
The bind operator effectively binds the value of `this` on the right-hand side function to the left-hand side value. And you wouldn't believe how many situations you'd use the new :: binding operator. There's the jQuery-like format:
// Find all the divs with class="myClass"
// then get all of the "p"s and replace their content.document.querySelectorAll("div.myClass")::find("p")::html("hahah")
When dealing with promises you can use of method extraction to print the eventual value of a promise to the console:
Promise.resolve(123).then(::console.log)
And when dealing with DOM events, use method extraction to call an object method when the event occurs:
$(".some-link").on("click", ::view.reset)
ES2016 Bind Syntax is quite a charming addition to the many new features of ECMAScript and I cannot wait for it to be completely supported in all major browsers. While that doesn't happen, you should check out the ES2017 Babel preset as it is already able to transpile a lot of the new features to ES5 code.
So that's binding.
Cheers,