Reusable React Code Modules, Part 6 - HTTP Client and Requests to APIs
Efficient Data Handling: Modular HTTP Clients for API Communication
Managing HTTP requests and interacting with APIs is a fundamental aspect of building front-end web applications. This guide covers how to structure reusable HTTP client modules in React, handle requests efficiently, and manage responses. We'll explore practical examples, recommended libraries, and code snippets. Additionally, we'll demonstrate how to create a class wrapper for individual APIs.
Common Libraries and Tools
1. Axios
Axios is a promise-based HTTP client for JavaScript that can be used in both the browser and Node
https://github.com/axios/axios
Key Features
Supports request and response interceptors
Automatic transformation of JSON data
CSRF protection
Supports cancellation of requests
2. Fetch API
The Fetch API is a native JavaScript API for making HTTP requests, available in most modern browsers
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
https://developer.mozilla.org/en-US/docs/Web/API/fetch
Key Features
Simple and native browser support
Supports promises for request handling
Streaming capabilities
No built-in support for request/response interception
3. React Query
React Query is a powerful data-fetching library for React that helps manage server state
https://tanstack.com/query/latest/docs/framework/react/overview
https://www.npmjs.com/package/react-query
Key Features
Caching and background updates
Automatic refetching
Pagination and infinite scrolling
Supports optimistic updates
4. Apollo Client
Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL
https://www.apollographql.com/docs/react
https://www.npmjs.com/package/@apollo/client
https://github.com/apollographql/apollo-client
Key Features
Declarative data fetching
Cache management
Optimistic UI updates
Subscription support
Comparison
Axios: Best for projects needing comprehensive request/response management and custom interceptors
Fetch API: Ideal for simple projects with native browser support
React Query: Suitable for projects requiring advanced server state management, caching, and background updates
Apollo Client: Best for applications using GraphQL for data fetching and management
Examples
Example 1: Axios
Setup:
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 1000,
headers: { 'Content-Type': 'application/json' },
});
apiClient.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
apiClient.interceptors.response.use(
response => response,
error => {
if (error.response.status === 401) {
console.error('Unauthorized');
}
return Promise.reject(error);
}
);
export default apiClient;
Usage:
import React, { useEffect, useState } from 'react';
import apiClient from './apiClient';
const Users = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
apiClient.get('/users')
.then(response => setUsers(response.data))
.catch(error => console.error(error));
}, []);
return (
<div>
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
};
export default Users;
Example 2: Fetch API
Utility Function:
const fetchData = async (url, options = {}) => {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
export default fetchData;
Usage:
import React, { useEffect, useState } from 'react';
import fetchData from './fetchData';
const Posts = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetchData('https://jsonplaceholder.typicode.com/posts')
.then(data => setPosts(data))
.catch(error => console.error(error));
}, []);
return (
<div>
<h1>Posts</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
export default Posts;
Example 3: React Query
Setup:
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
const queryClient = new QueryClient();
const fetchUsers = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};
const Users = () => {
const { data, error, isLoading } = useQuery('users', fetchUsers);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>Users</h1>
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
};
const App = () => (
<QueryClientProvider client={queryClient}>
<Users />
</QueryClientProvider>
);
export default App;
Example 4: Apollo Client
Setup:
import React from 'react';
import { ApolloClient, InMemoryCache, ApolloProvider, useQuery, gql } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://api.spacex.land/graphql/',
cache: new InMemoryCache(),
});
const GET_LAUNCHES = gql`
query GetLaunches {
launchesPast(limit: 10) {
mission_name
launch_date_local
launch_site {
site_name_long
}
links {
article_link
video_link
}
rocket {
rocket_name
}
}
}
`;
const Launches = () => {
const { loading, error, data } = useQuery(GET_LAUNCHES);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h1>SpaceX Launches</h1>
<ul>
{data.launchesPast.map(launch => (
<li key={launch.mission_name}>
<p>{launch.mission_name}</p>
<p>{launch.rocket.rocket_name}</p>
</li>
))}
</ul>
</div>
);
};
const App = () => (
<ApolloProvider client={client}>
<Launches />
</ApolloProvider>
);
export default App;
Creating a Class Wrapper for API Clients
Class Wrapper for Axios
API Client Class:
import axios from 'axios';
class ApiClient {
constructor(baseURL) {
this.client = axios.create({
baseURL,
timeout: 1000,
headers: { 'Content-Type': 'application/json' },
});
this.client.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
this.client.interceptors.response.use(
response => response,
error => {
if (error.response.status === 401) {
console.error('Unauthorized');
}
return Promise.reject(error);
}
);
}
get(url, params = {}) {
return this.client.get(url, { params });
}
post(url, data) {
return this.client.post(url, data);
}
put(url, data) {
return this.client.put(url, data);
}
delete(url) {
return this.client.delete(url);
}
}
export default ApiClient;
Usage:
import React, { useEffect, useState } from 'react';
import ApiClient from './ApiClient';
const apiClient = new ApiClient('https://api.example.com');
const Users = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
apiClient.get('/users')
.then(response => setUsers(response.data))
.catch(error => console.error(error));
}, []);
return (
<div>
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
};
export default Users;
Class Wrapper for Fetch API
API Client Class:
class FetchApiClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(url, options = {}) {
const response = await fetch(`${this.baseURL}${url}`, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
get(url) {
return this.request(url, { method: 'GET' });
}
post(url, data) {
return this.request(url, {
method: 'POST',
body: JSON.stringify(data),
});
}
put(url, data) {
return this.request(url, {
method: 'PUT',
body: JSON.stringify(data),
});
}
delete(url) {
return this.request(url, { method: 'DELETE' });
}
}
export default FetchApiClient;
Usage:
import React, { useEffect, useState } from 'react';
import FetchApiClient from './FetchApiClient';
const apiClient = new FetchApiClient('https://jsonplaceholder.typicode.com');
const Posts = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
apiClient.get('/posts')
.then(data => setPosts(data))
.catch(error => console.error(error));
}, []);
return (
<div>
<h1>Posts</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
export default Posts;