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.
Originally posted on Nordschool.
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
Enjoyed the article? Share the summary thread on twitter.
Nordschool@nordschoolMostΒ commonΒ APIΒ dataΒ fetchingΒ patternsΒ inΒ @reactjs π
- Standalone
- HOC
- WithΒ Hooks
- RenderΒ Props
- ReduxΒ CustomΒ Middleware
THREAD... π08:38 AM - 23 Oct 2019
Better Code Monday Newsletter
You might also like my newsletter. The idea is to share 3 web dev tips every Monday.
My goal is to improve my writing skills and share knowledge as much as possible. So far, few hundreds of developers have subscribed and seem to like it.
To get a feeling of what kind of stuff I share, Check out the previous newsletter issues and subscribe.
Top comments (2)
Great Overview - helped me also to understand the benefits of hooks.
Cheers! π