Promises were a revolutionary addition to the JavaScript programming language. Before promises, it was necessary to pass callbacks into function calls in order to model asynchronous control-flow. Promises give you a much more readable and maintainable way to manage asynchronous actions in your React app. The then
and catch
functions that a promise provides enable you to chain functions asynchronously and handle errors. However, using promises in your React components can be confusing when it comes to scope and the this
keyword. In JavaScript, the this
keyword allows you to access the context object of the surrounding lexical scope. In this guide, you will learn how to ensure that your promise catch
blocks use the right context within your React components so that you can easily access component properties via this
.
Let's dive in!
First, the problem. When using promises in your React component, your first instinct may be to handle a promise like this:
1// ... MammalComponent.jsx
2
3fetchMammals() {
4 return fetch(this.mammalsUrl)
5 .then(function(response) {
6 return response.json();
7 });
8}
9
10initMammalsState() {
11 const mammalJsonPromise = this.fetchMammals();
12 mammalJsonPromise
13 .then(function(mammals) {
14 this.setState({ mammals });
15 })
16 .catch(function(error) {
17 this.error = error;
18 });
19}
20
21
22// ...
At first glance, this code seems like it would work. It's simply using the Fetch
API to grab some JSON and then handle the promise returned. It also sets the error
property on the component itself in the event an error occurs in the catch
block. But unfortunately, this code fails! If you create a component using the code above, you will likely see an error that says something akin to Can't find property <prop-name> of undefined
. This is because your promise doesn't have access to the right this
context!
The first way you can solve this issue is by using the bind
function that is available on JavaScript's Function
prototype. The bind
function lets you inject a this
context into the scope of the function you are working with. Below, you will find the functions above refactored in order to take advantage of this capability. Note how even the component functions themselves are bound to the React component's this
context in the constructor.
1// ... MammalComponent.jsx
2
3constructor(props) {
4 super(props);
5
6 // Bind the 'this' context of the component to our class functions
7 this.fetchMammals = this.fetchMammals.bind(this);
8 this.initMammalsState = this.initMammalsState.bind(this);
9}
10
11fetchMammals() {
12 return fetch(this.mammalsUrl)
13 .then(function(response) {
14 return response.json();
15 });
16}
17
18initMammalsState() {
19 const mammalJsonPromise = this.fetchMammals();
20 mammalJsonPromise
21 .then(function(mammals) {
22 this.setState({ mammals });
23 }.bind(this))
24 .catch(function(error) {
25 this.error = error;
26 }.bind(this));
27}
28
29
30// ...
In this naive solution above, you can see how the bind
function is being used to bind the context of this into the catch
block so that the error can be shown by the component. This works ... but there's a better way! In the next section, you will see how arrow functions can be used to greatly simplify this.
Arrow functions are so much more than just succinct means of declaring functions. Arrow functions also implicitly bind the this
keyword for you! In the following code, you will see how we can access the error
property of our MammalsComponent
through arrow functions.
1// ... MammalComponent.jsx
2
3// We don't have to bind these functions anymore!
4fetchMammals = () => fetch(this.mammalsUrl).then(response => response.json())
5
6initMammalsState = () => {
7 const mammalJsonPromise = this.fetchMammals();
8
9 mammalJsonPromise
10 .then(mammals => this.setState({ mammals }))
11 .catch(error => this.error = error);
12}
13
14// ...
Wow! Look at how much simpler the code is! This code not only works, but is cleaner and more maintainable. The catch
block now has access to the MammalComponent
's scope so that it now can set the error
property.
Arrow functions are the best way to bind the context of a React component's scope to inner functions within the component. Using arrow functions in this fashion allows you to effectively access the this
property within promise catch
blocks.
There are some additional improvements that could be made to the above code, namely the use of async/await syntax. The async and await JavaScript keywords can allow you to simplify promise usage and cut down on the amount of nested code you have in your codebase. It also allows you to model asynchronous code in a synchronous manner. For more information, check out the documentation for async/await syntax.