Using Async/Await in React
If you’ve worked with JavaScript for any amount of time, you’ll know that by nature it’s a synchronous language. This means that it moves forward in a logical pattern, executing functions and lines of code in the order delivered to it, and not moving on to the next function until it’s completed the previous. Granted, we can define that order non-linearly by calling an already defined function from another function placed “lower” in our code, but JavaScript moves from block to block until it’s done.
Synchronous flow is great for functions that instantly return a value that can be applied or used for the next function. But what if the data we’re asking our function to find takes longer to retrieve? Either we’ll have to wait an undefined amount of time before our next function runs, or — in the case of React rendering — a function that’s dependent on the data loading might run without the data that it needs.
This is problem we often run into when requesting data from an external source such as an API, that needs to be retrieved before running the next function that, for example, displays said data to the user. How can we solve this? Async/await functions.
Async/Await
Asynchronous functions allow our code to keep moving while one function is still waiting for it’s return value, or when using JavaScript’s async and await reserved words, to ensure certain lines don’t run until the function returns a value.
Using async when defining a function tells JavaScript’s engine to treat it as asynchronous function, and using await before calling an internal function puts everything on pause until the function returns a value.
Let’s look at an example in React (note that we’re using Redux to access actions and store props, which we won’t go into here). In the component MovieShow, we want to fetch data from an external movie database and then display the details for a particular movie (title & movie poster). We’ll add our fetch function within the componentDidMount() event so that it loads before rendering the component. This works well, but we also want to display a CreditsList component, which displays a list of all the movie’s cast.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as actions from '../actions/movieActions'import CreditsList from './CreditsList'class MovieShow extends Component { componentDidMount() {
this.props.actions.fetchMovie(this.props.id)
} render() {
const { title, poster_path } = this.props.movie.details
const cast = this.props.movie.credits.cast return (
<div>
<h1>{title}</h1>
<img src={poster_path} alt={title}/> <CreditsList credits={cast} />
</div>
)
}function mapStateToProps(state, ownProps) {
return {
id: ownProps.match.params.movieId,
movie: state.selected_movie
}
}function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actions, dispatch) }
}export default connect(mapStateToProps, mapDispatchToProps)(MovieShow)
However, we quickly notice a glaring issue: the CreditsList component is not displaying! Or even worse, the map() feature within the component that displays each credit entry has no data to pull from and crashes our app. What’s happening?
With React’s normal flow, it tries to render the current component’s children straight away. But in this case, our fetch function hasn’t completed grabbing all the necessary information, which breaks the child component and ultimately everything. Let’s fix this with await/async and component state:
...class MovieShow extends Component { constructor() {
super()
this.state = {
loaded: false
}
} async componentDidMount() {
await this.props.actions.fetchMovie(this.props.id)
this.setState({ loaded: true })
} render() {
const { title, poster_path } = this.props.movie.details
const cast = this.props.movie.credits.cast return (
<div>
{this.state.loaded ?
<div>
<h1>{title}</h1>
<img src={poster_path} alt={title}/> <CreditsList credits={cast} />
</div>
: null }
</div>
)
}...
Breaking down the above changes, we:
- Set the component’s default state item “loaded” to “false.”
- Added the word “async” before defining componentWillMount().
- Added the word “await” before calling fetchMovie().
- Set the state item “loaded” to “true” after fetchMovie() returns a value.
- Added conditional rendering to display the movie details and CreditsList component only when “loaded” equals “true.” Otherwise, display nothing.
Async/await is comparable to promise/then, another technique in JavaScript to deal with asynchronous functions. While there’s a place for each (fetch() for example tends to work better with promises), I prefer the async/await syntax in the context of React components, as it keeps the code clean and readable.