React Tutorial: Loading and Displaying Data with AJAX

A lot of starter React tutorials demonstrate the basics how to define and use JSX components. But there are a number of other dimensions in a full production web application. One common feature is loading data from a remote API using technology like AJAX.

React is considered to be a library, not a framework. It does one thing really well: create reusable components that accept data and construct the view of a web app. It relies on outside packages to do many other things that frameworks like Angular and Backbone come with built in. One key feature which comes in an outside package is AJAX, which provides a way to download data from outside of the app. With React, it’s very easy to integrate the the JSX markup in components with the data returned by the package making the AJAX request.

Below is the full code for a simple application, a Contact List with Contact objects, that loads data using the Axios package, a lightweight AJAX library.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<div id="root"></div>  <!-- 1 -->
<script type="text/babel">
    class ContactList extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                contacts: []
            };
        }
        componentDidMount() {
            let dataPath = "data/contactdata.json";
            axios.get(dataPath)    // 2
                .then( resp => {    // 3
                    const contacts = resp.data.slice();   // 4
                    this.setState({ contacts });
                })
                .catch( err => {
                    let resp = err.response;
                    alert("Error loading data: " + resp.status + " " +
                        resp.statusText + ": " + resp.config.url);
                });
        }
        render() {
            return (
                <div>
                    <h2>Contact List</h2>
                    {this.state.contacts.map(contact =>
                        <Contact data={contact} key={contact.id} />  // 5
                    )}
                </div>
            );
        }
    }
 
    class Contact extends React.Component {
        constructor(props) {
            super(props);
            this.state = {...props.data};  // 6
            this.doClick = this.doClick.bind(this);
        }
        doClick(evt) {
            alert(this.state.firstName + ' ' + this.state.lastName);
        }
        render() {    // 7
            return (
                <div onClick={this.doClick}>
                    Contact: {this.state.firstName} {this.state.lastName},&nbsp;
                    {this.state.phoneNumber}
                </div>
            )
        }
    }
 
    ReactDOM.render(
        <ContactList/>,
        document.getElementById('root')
    );
</script>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script src="https://unpkg.com/axios@0.18.0/dist/axios.min.js"></script>

You can see and play with a codepen of this code here.

The data returned from the AJAX call is an array of data objects that looks like this:

1
2
3
4
5
6
7
8
9
10
11
[
    {
	"id": 1111,
	"firstName": "Arthur",
	"lastName": "Dent",
	"emailAddress": "arthur@earthling.net",
	"phoneNumber": "617-555-1234",
	"websiteUrl": "http://www.earthling.net/adent",
	"pin": "1234"
    },
    ...more data...

The goal is to fetch this data and display some of it (firstName, lastName, and phoneNumber) in a browser page. Here’s a step-by-step breakdown of how this example works:

Step 1: a <div> with id “root” is defined: this is where the ContactList data will be displayed.

Step 2: The Axios library is used here, making an “axios.get()” call to get the JSON data. This call is made in the “componentDidMount()” lifecycle method of the ContactList component, which is called when the component is first displayed.

Step 3: The “.then()” function, called asynchronously on completion of the get() request, takes an ES6 arrow function that processes the data.

Step 4: The arrow function sets the state of the component to the data returned from the AJAX call, using the setState() method. Calling setState() has the side effect of informing the component that the render() method has to be called to display the new state. The Array.slice() method is used here to make a copy of the array of JSON data to promote immutability in the code (more on that below).

Step 5: The ContactList.render() method iterates over each element of the contacts data array using the Array.map() function. The arrow function that’s passed to .map() creates an instance of a “<Contact>” component for each element of the array. The data is passed to each instance of Contact component as a “data” attribute.

Step 6: The state of each Contact is set in its constructor() method, using data that was passed to it from the ContactList component. Again, the data is copied for immutability, in this case using ECMAScript Object expand syntax.

Step 7: In the Contact.render() method, the visual representation of a contact is defined using a “<div>”, inserting data from the contact state using curly brace notation. This demo code also defines an onClick() handler on each Contact to display the name of the contact when clicked – just a brief demonstration of how event handling works in React.

For the purposes of this demo the data is simply a fixed json file, but in practice this would be an API endpoint served by something like an express server that in turn fetches data from a relational or NoSQL database. There might also be some filtering or refinement of the data rather than a direct assignment to state as happens in this demo.

Enforcing Immutability of Component State

At step 4 of the demo code, the data returned from the AJAX request is assigned to a const variable and then set as the state of the ContactList component. The data that’s returned is an array, and the slice() function (with no arguments) is used to make a copy of that data array. This helps keep the component immutable: no code outside the component has a reference to any data that’s kept in the state of the component, so only code within the component can make changes to it. This is a key concept in React programming; it makes the code easier to understand, inherently concurrency-safe, and facilitates “time travel” of all states of the component, which can help debug the code.

Similarly, in step 6 immutability is enforced on the instance of contact data passed to the Contact component using the object spread operator (“{…obj}”) to make a copy of it: a new object is created with the same properties and values as the old object. Some code examples will use “Object.assign(obj)” instead of object spread to copy an object in situations like this, but Object.assign() is not supported by Internet Explorer. The Object spread operator will work as long as a relatively recent version of the Babel transpiler is being used (either downloaded from a CDN like unpkg.com or incorporated as part of the build process in a full development environment using a tool like webpack).

Keeping Unique Keys in Collections of Components

One point to notice in step 5 of the above code: where the Contact components are being created in the ContactList render() method, an additional “key” property is being created for each Contact. React uses this key to determine when unique instances of components are added, removed, or changed in a collection of components like this. It’s standard practice to always define keys on objects in a collection. This makes display and update of collections of components more efficient and eliminates some weird and difficult to debug display issues.

If no key is defined, React will use a consecutive integer for each newly created object instance, and a warning will be displayed in the dev console saying, “Each child in an array or iterator should have a unique key prop”.

The “id” property for each contact is hard coded in the sample data being used in this demo, but in a real application this would be a unique ID from the database of contacts.

The keys defined for a collection of objects only has to be unique within the collection; they do not have to be globally unique across the application.

Conclusion

There’s a natural flow of the data and view creation in this app: The <ContactList> component is created, it makes the AJAX request in the componentDidMount() lifecycle method, and then state is set in the response to the AJAX request. Setting (changing) the state forces the render() method to be called on the ContactList object, and the render method pushes data down to instances of the Contact class, passing it as an attribute on the <Contact> tag in the JSX code. The Contact class accepts that data, sets state, the render() method gets called and the data is inserted into the JSX for the Contact. There’s a natural interweaving of data into views in the calls between Contact List and Contact.

TL;DR

Loading data from a remote server is a basic feature of many web applications. In React you would do the following:

  • Find and use a third part AJAX library like “fetch” or “axios” (use yarn or npm to install these by name for local development)
  • Make the HTTP request (like .get() or .post()) in the React “componentDidMount()” lifecycle method
  • In the handler function for the HTTP request, call “this.setState(data)” to save the returned data to the component’s state
  • In the component render() function, use “{this.state.<myvarname>}” to interpolate values into the component’s JSX markup
  • If the data is an array, the Array.map() function can be used to iterate over each element of the array when inserting markup

Use Array.slice() and the Object spread operator to copy data that comes into a ReactJS component to enforce mutability, and define unique “key” attributes for components that are members of a collection to improve performance and reduce bugs.

References

For more information on why keys are important in collections of components, see the discussion of
Recursing on Children (a.k.a. the “reconciliation algorithm”) in the ReactJS documentation.

Versions

React version 16
Babel v6.15.0
Axios 0.18.0

Add a Comment

Your email address will not be published.