5 React Data-Fetching Patterns

Cover image

Sooner or later you will need to fetch data from an API, let's have a look at how we can handle data in React!👌

In this tutorial, we will cover the most common data fetching patterns in React.

Are you read? Let's do this! 💪

Overview

Let's have a look at the bigger picture first and then dig deeper.

The patterns we will cover:

Project structure

I have created a small react project to show different data fetching patterns. The project was initialized create-react-app has a standard structure. 👇

├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── actions
│   │   ├── api.js
│   │   ├── index.js
│   │   └── types.js
│   ├── hooks
│   │   ├── UseDataApi.js
│   ├── components
│   │   ├── HOC.js
│   │   ├── Standalone.js
│   │   ├── PostsList.js
│   │   ├── RenderProps.js
│   │   ├── WithCustomMiddleware.js
│   │   ├── WithCustomHook.js
│   │   └── WithHooks.js
│   ├── index.css
│   ├── index.js
│   ├── middleware
│   │   └── api.js
│   ├── reducers
│   │   └── index.js
│   ├── serviceWorker.js
│   └── store
│       └── index.js
└── yarn.lock

We will be dealing with components.

Here is how the main root component looks like:

// App.js
import React from 'react';
import './App.css';

import Standalone from './components/Standalone';
import HOC from './components/HOC';
import WithHooks from './components/WithHooks';
import WithCustomHook from './components/WithCustomHook';
import RenderProps from './components/RenderProps';
import WithCustomMiddleware from './components/WithCustomMiddleware';
import PostsList from './components/PostsList';

function App() {
  return (
    <div className="App">
      <h3>Standalone</h3>
      <Standalone />
      <h3>HOC</h3>
      <HOC />
      <h3>WithHooks</h3>
      <WithHooks />
      <h3>With Custom Hook</h3>
      <WithCustomHook />
      <h3>Render Props</h3>
      <RenderProps children={PostsList} />
      <h3>With Custom Middleware</h3>
      <WithCustomMiddleware />
    </div>
  );
}

export default App;

Let's start with the most compact pattern...

Standalone

This standalone component handles both fetching and rendering the data.

// components/Standalone.js
import React, { Component } from 'react';
import axios from 'axios';

import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';

// API END POINT
const POSTS_SERVICE_URL = 'https://jsonplaceholder.typicode.com/posts';

class Standalone extends Component {
  state = {
    // Initial state.
    isFetching: false,
    posts: []
  };

  render() {
    return (
      <Paper>
        <Table aria-label="simple table">
          <TableHead>
            <TableRow>
              <TableCell>Id </TableCell>
              <TableCell>Title</TableCell>
              <TableCell>Body</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {this.state.posts.map(row => (
              <TableRow key={row.id}>
                <TableCell component="th" scope="row">
                  {row.id}
                </TableCell>
                <TableCell>{row.title}</TableCell>
                <TableCell>{row.body}</TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
        <p>{this.state.isFetching ? 'Fetching posts...' : ''}</p>
      </Paper>
    );
  }

  componentDidMount() {
    this.fetchPosts();
  }

  async fetchPostsAsync() {
    try {
      this.setState({ ...this.state, isFetching: true }); // Sets loading state.
      const response = await axios.get(POSTS_SERVICE_URL);
      this.setState({
        ...this.state,
        isFetching: false,
        posts: response.data.slice(0, 5) // Take first 5 posts only
      });
    } catch (e) {
      console.log(e);
      this.setState({ ...this.state, isFetching: false });
    }
  }

  fetchPosts = this.fetchPostsAsync;
}

export default Standalone;

Let's see if we can separate the view from the data fetching 🤓...

HOC

The Higher-Order Component (HOC) pattern is common in React. Components like this sometimes referred to as container components.

The idea is straight forward, data-fetching gets separated from data-presentation.

// HOC.js
import React, { Component } from 'react';
import Simple from './Simple';

import axios from 'axios';

const POSTS_SERVICE_URL = 'https://jsonplaceholder.typicode.com/posts';

class HOC extends Component {
  state = {
    isFetching: false,
    posts: []
  };

  render = () => (
    <Simple data={this.state.posts} isFetching={this.state.isFetching}></Simple>
  );

  componentDidMount() {
    this.fetchPosts();
  }

  async fetchPostsAsync() {
    try {
      this.setState({ ...this.state, isFetching: true });
      const response = await axios.get(POSTS_SERVICE_URL);
      this.setState({
        ...this.state,
        isFetching: false,
        posts: response.data.slice(0, 5)
      }); // Take first 5 posts only
    } catch (e) {
      console.log(e);
      this.setState({ ...this.state, isFetching: false });
    }
  }

  fetchPosts = this.fetchPostsAsync;
}

export default HOC;

The presentation component would look something like this:

// PostsList.js
import React from 'react';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';

const PostsList = props => {
  return (
    <Paper>
      <Table aria-label="simple table">
        <TableHead>
          <TableRow>
            <TableCell>Id </TableCell>
            <TableCell>Title</TableCell>
            <TableCell>Body</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {props.data.map(row => (
            <TableRow key={row.id}>
              <TableCell component="th" scope="row">
                {row.id}
              </TableCell>
              <TableCell>{row.title}</TableCell>
              <TableCell>{row.body}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
      <p>{props.isFetching ? 'Fetching posts...' : ''}</p>
    </Paper>
  );
};

export default PostsList;

But what if I told you that there might be even a better way of doing things? 😁

Let's have a look at how that would work with Hooks.

With Hooks

This pattern is similar to HOC but it uses a functional component together with hooks.

// WithHooks.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Simple from './Simple';

const POSTS_SERVICE_URL = 'https://jsonplaceholder.typicode.com/posts';

function WithHooks() {
  const [data, setData] = useState({ posts: [], isFetching: false });

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        setData({ ...data, isFetching: true });
        const response = await axios.get(POSTS_SERVICE_URL);
        setData({
          ...data,
          posts: response.data.slice(0, 5),
          isFetching: false
        });
      } catch (e) {
        console.log(e);
        setData({ ...data, isFetching: false });
      }
    };
    fetchUsers();
  }, []); // Runs once

  return <Simple data={data.posts} isFetching={data.isFetching} />;
}

export default WithHooks;

We can take this a step further and even create a generic hook to fetch data from any API.

A simple version of that generic data fetching hook:

// hooks/UseDataApi.js
import { useEffect, useState } from 'react';
import axios from 'axios';

const useDataApi = url => {
  // This is just for demo purposes, you probably want to separate the data from loading state and potentially add other states such as failures, etc..
  const [dataState, setDataState] = useState({ data: [], isFetching: false }); 
  const [endpointUrl] = useState(url);

  useEffect(() => {
    const fetchDataFromApi = async () => {
      try {
        setDataState({ ...dataState, isFetching: true });
        const response = await axios.get(endpointUrl);
        setDataState({
          ...dataState,
          data: response.data,
          isFetching: false
        });
      } catch (e) {
        console.log(e);
        setDataState({ ...dataState, isFetching: false });
      }
    };
    fetchDataFromApi();
  }, []); // Runs once

  return [dataState];
};

export default useDataApi;

Once you have your hook ready, it can be used like this...

// components/WithCustomHook.js
import React from 'react';
import UseDataApi from '../hooks/UseDataApi';
import PostsList from './PostsList';

const POSTS_SERVICE_URL = 'https://jsonplaceholder.typicode.com/posts';

function WithHooks() {
  const [dataState] = UseDataApi(POSTS_SERVICE_URL);

  return (
    <PostsList
      data={dataState.data.slice(0, 5)}
      isFetching={dataState.isFetching}
    />
  );
}

export default WithHooks;

We recommend this pattern for most cases! This pattern is reusable and does separation of concern well.

Ok, so far so good.

But, what if you have many presentation components that display the same data?

Generally speaking, Hooks can be cover most of your logic-encapsulation cases. But, it may have a few limitations.

Render Props

You could use hooks instead of render props for this use case but render props is another viable option.

Render props act as a reusable wrapper for different presentation components.

// RenderProps.js
import { Component } from 'react';
import axios from 'axios';

const POSTS_SERVICE_URL = 'https://jsonplaceholder.typicode.com/posts';

class RenderProps extends Component {
  state = {
    isFetching: false,
    data: []
  };

  render = () => this.props.children(this.state);

  componentDidMount() {
    this.fetchPosts();
  }

  async fetchPostsAsync() {
    try {
      this.setState({ ...this.state, isFetching: true });
      const response = await axios.get(POSTS_SERVICE_URL);
      this.setState({
        ...this.state,
        isFetching: false,
        data: response.data.slice(0, 5)
      }); // Take first 5 posts only
    } catch (e) {
      console.log(e);
      this.setState({ ...this.state, isFetching: false });
    }
  }

  fetchPosts = this.fetchPostsAsync;
}

export default RenderProps;

That is all for Vanilla React patterns! 🙌

Ok what about Redux, how would this look like? Let me give you a sneak peek.

Redux Custom Middleware

Here is a simple example using a custom middleware.

// components/WithCustomMiddleware.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import PostsList from './PostsList';
import { fetchPosts } from '../actions';

class WithCustomMiddleware extends Component {
  state = {};

  componentDidMount() {
    this.props.fetchPosts();
  }

  render = () => (
    <PostsList
      data={this.props.data}
      isFetching={this.props.isFetching}
    ></PostsList>
  );
}

const mapStateToProps = ({ data = [], isFetching = false }) => ({
  data,
  isFetching
});

export default connect(
  mapStateToProps,
  { fetchPosts }
)(WithCustomMiddleware);

Not sure how does the Redux middleware work? check out this short tutorial about how to create a custom redux middleware.

That is it, now you know how to handle data fetching in React! ✋

Support us

Enjoyed the article? Share the summary thread on twitter.



Author image

Learn how to build scalable, fast and accessible web applications.

Follow us on Twitter

hello@nordschool.com