Author avatar

Gaurav Singhal

Hierarchy of Components and How to Get Async Data

Gaurav Singhal

  • Jan 23, 2020
  • 11 Min read
  • 2,373 Views
  • Jan 23, 2020
  • 11 Min read
  • 2,373 Views
Web Development
React

Introduction

React makes it easy for us to display data in the view. There are different component hierarchies that we can follow for displaying the data. In this guide, we are going to see some of these component hierarchy structures and learn how to fetch async data, show a loading indicator until the data is loaded to enhance the user experience, and load more data as the user scrolls to the bottom.

Async Data Fetching

First, we will fetch data into our application. In React,, there are two methods to get asynchronous data: The fetch API and the Axios library.

Browser Fetch API

The fetch() method is natively available in browsers, just like XMLHttpRequest (XHR). The significant difference between these two is that the fetch API uses Promises, which is a lot easier to work with and has a cleaner syntax compared to XHR.

A basic fetch() request is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
const URL = "www.example.com/posts";

fetch(URL)
  .then(function(response) {
    // parse the response into suitable format
    // response.text()
    response.json();
  })
  .then(function(data) {
    // actual data is passed here
    console.log(data);
  });
js

The fetch() method returns a Stream object, which means that we can parse the stream to json, text, or blob. To parse the stream to json, we can use the json() method, which returns a Promise, which contains the actual data on success.

We can further simplify the syntax and avoid the callbacks using the ES6 async-await syntax.

1
2
3
4
5
6
7
8
9
async function getPosts() {
  const URL = "www.example.com/posts";

  const response = await fetch(URL);
  const data = await response.json();

  console.log(data);
  return data;
}
js

By default, fetch() makes a GET request to the URL. You can specify the method in the config object and pass that in as the second argument.

1
2
3
4
5
6
7
8
9
async function addPost(postData) {
  const URL = "www.example.com/posts";

  const response = await fetch(URL, { method: "POST", data: postData });
  const data = await response.json();

  console.log(data);
  return data;
}
js

Axios JavaScript Library

Axios is a popular Javascript library used to perform HTTP requests, similar to the fetch API. The main difference between the two is that Axios does the automatic transformation to JSON internally and cuts down on the two-step process to get the actual data in fetch.

A basic HTTP GET request with Axios can be done as follows:

1
2
3
4
5
6
7
8
9
10
11
12
function getPosts() {
  const URL = "www.example.com/posts";
  axios.get(URL).then(response => console.log(response.data));
}

// using async-await

async function getPosts() {
  const URL = "www.example.com/posts";
  const { data } = await axios.get(URL);
  return data;
}
js

For more information on Axios, please refer to this guide.

Note that, for the sake of brevity, I've not written the async calls in a try-catch block. You must encapsulate code in try-catch whenever dealing with network requests, as sometimes the request may fail due to network error or server issues.

Component Hierarchy

Now that we know how to fetch asynchronous data in React, let's take a look at how we can display data in the component and some of the hierarchies of components.

Single Component

In a single-component hierarchy, data is fetched and presented in the same component. Components can fetch data on a button click once it's mounted using the componentDidMount() lifecycle method, or periodically to keep it in sync with the server.

Let's take a look at how we can fetch data using the componentDidMount() method.

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
class Posts extends Component {
  state = { posts: [] };

  async componentDidMount() {
    const posts = await getPosts();
    this.setState({ posts });
  }

  render() {
    return (
      <div class='posts-container'>
        {this.state.posts.map(post => (
          <div class={`post post-id-${post.id}`}>
            <div class='post-image'>
              <img src={post.imageUrl}>
            </div>
            <div class='post-title'>
              <h4> {post.title} </h4>
            </div>
          </div>
        ))}
      </div>
    )
  }
}
jsx

Since getPosts() is an async function, it will return a Promise, so I've used the async-await syntax. In the next line, I've updated the state to hold the posts. In the render() method, I've used the Array.map() method to display each post.

Now, let's do the same using the latest React Hooks API. React Hooks enable use to have state inside of a functional component.

1
2
3
4
5
6
7
import React, { useState } from "react";

const Posts = () => {
  const [posts, setPosts] = useState([]);

  // return
};
jsx

The useState() method declares a state variable and a method that can be used to change the state variable. We pass in the initial state to the useState() method, in our case, an empty array. It returns an array consisting of two values: the initial state and the method that modifies it. In our example, the values are stored in posts and setPosts, respectively.

To perform async data fetching, we use the useEffect hook function.

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
import React, { useState, useEffect } from "react";

const Posts = () => {
  const [posts, setPosts] = useState([]);

  useEffect(() => {

    (async () => {
      const fetchedPosts = await getPosts();
      setPosts(fetchedPosts);
    })();

  }, []);

  return (
      <div class='posts-container'>
        {posts.map(post => (
          <div class={`post post-id-${post.id}`}>
            <div class='post-image'>
              <img src={post.imageUrl}>
            </div>
            <div class='post-title'>
              <h4> {post.title} </h4>
            </div>
          </div>
        ))}
      </div>
    )
};
jsx

The useEffect() function takes in a function as the first argument, and the second argument is an array of all variables the function depends on. In our case, it's important to pass an empty array to the hook; otherwise, the function will execute each time the component updates. This is a nasty bug, and we should avoid it. You must have noticed that I wrapped the async-await block in a self-invoking function. That's because the useEffect function must not return a Promise object, and it will throw an error if you try to do so.

Higher-order Component (HOC)

In a higher-order component hierarchy, data is fetched in the parent component and passed down as props to a child component. Further components nested in the child component can receive different parts of the fetched data.

The idea is to separate the data-fetching logic from the view and focus more on handling or presenting the data to the user. More emphasis here is given to SOC, or separation of concerns.

So in our parent component, we will have the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Posts = () => {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    (async () => {
      const fetchedPosts = await getPosts();
      setPosts(fetchedPosts);
    })();
  }, []);

  return (
    <div class="posts-container">
      {this.state.posts.map(post => (
        <Post post={post} />
      ))}
    </div>
  );
};
jsx

And in our child component we will write the template to display the post :

1
2
3
4
5
6
7
8
9
10
const Post = {post} => (
  <div class={`post post-id-${post.id}`}>
    <div class='post-image'>
      <img src={post.imageUrl}>
    </div>
    <div class='post-title'>
      <h4> {post.title} </h4>
    </div>
  </div>
)
jsx

As you can see, the parent component is now lighter and more readable compared to the single component hierarchy. This way, we can separate the presentation components from the container component. Presentation components are more driven towards generating the markup to output the data, whereas container components are responsible for fetching and handling the data from the backend API.

Render Props Component

In a render props component hierarchy, a component takes in a function that returns a React element as a prop and calls it in its render() function instead of implementing its render logic.

Consider this example. Let's say that, along with posts, you also have a component that displays user data. In that case, you would want to encapsulate and reuse the code between the two components. For that purpose, we will create a <FetchComponent /> that will take the URL as a prop along with a render function.

1
2
3
4
5
// for displaying posts
<FetchComponent url="http://example.com/posts" render={post => <Post post={post} />} />

// for displaying users
<FetchComponent url="http://example.com/users" render={user => <User user={user} />} />
jsx

Now, let's write the <FetchComponent />.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const FetchComponent = props => {
  const [data, setData] = setState([]);

  useEffect(() => {
    (async () => {
      const response = await axios.get(props.url);
      setData(response.data);
    })();
  }, []);

  return (
    <div class="data-container">
      {data.map(dataItem => props.render(dataItem))}
    </div>
  );
};
jsx

Wouldn't it be nice to show a loading indicator? Let's do that.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const FetchComponent = props => {
  const [data, setData] = setState([]);
  const [isLoading, setLoading] = setState(false);

  useEffect(() => {
    (async () => {
      setLoading(true);
      const response = await axios.get(props.url);
      setLoading(false);
      setData(response.data);
    })();
  }, []);

  if(isLoading) return <Loader>
  return (
    <div class="data-container">
      {data.map(dataItem => props.render(dataItem))}
    </div>
  );
};
jsx

Render props component hierarchy can be used to create reusable and powerful components by hiding the complexity and thus helps you write readable and maintainable code.

We can make our <FetchComponent /> even more organized and maintainable by encapsulating the fetch logic in a custom React hook.

Let's create a useFetch hook.

useFetch.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { useState, useEffect } from "react";

export function useFetch(url) {
  const [data, setData] = setState([]);
  const [isLoading, setLoading] = setState(false);

  useEffect(() => {
    (async () => {
      setLoading(true);
      const response = await axios.get(props.url);
      setLoading(false);
      setData(response.data);
    })();
  }, []);

  return [data, isLoading];
}
jsx

In our <FetchComponent /> we can trim the code and instead use our custom hook function.

FetchComponent.js

1
2
3
4
5
6
7
8
9
10
11
12
import {useFetch} from './useFetch.js';

const FetchComponent = props => {
  const [data, isLoading] = useFetch(props.url);

  if(isLoading) return <Loader>
  return (
    <div class="data-container">
      {data.map(dataItem => props.render(dataItem))}
    </div>
  );
};
jsx

Conclusion

In this guide, we learned how to handle the complexity of data fetching and rendering the data into a view using best practices in terms of component hierarchies. You must write code with best and industry-standard practices.

That's it from this guide. I hope you liked it. Let me know your thoughts. Until next time, code like a beast.

7