Ram Kumar

Dec 09, 2024 • 7 min read • 

How to Fetch Data in React Efficiently: A Detailed Guide

If you are a front-end developer, understanding how to connect with APIs is an essential skill.

How to Fetch Data in React Efficiently: A Detailed Guide

Introduction

If you are a front-end developer, understanding how to connect with APIs is an essential skill. Fetching data, storing it, and using it to display dynamic content on the web is a core part of building interactive web applications.

In this guide, we’ll explore how to fetch data using two popular methods: the Fetch API and Axios. Along the way, we’ll discuss their differences, when to use them, and additional tools that can further optimize your app’s performance.

Fetching Data Using Fetch API

What is Fetch API?

The Fetch API is a built-in JavaScript method for making HTTP requests. It is supported by modern browsers and provides a promise-based approach to fetching data. With Fetch, you can make requests to RESTful APIs, retrieve data, and work with the response.

Basic Example with Fetch API

To fetch data in React, you’ll commonly use the useEffect hook alongside the Fetch API. Here’s a basic example of fetching user data:

import { useEffect, useState } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then((response) => response.json())
      .then((data) => setUsers(data));
  }, []);

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
export default UserList;

Error Handling in Fetch API

One limitation of Fetch is that it doesn’t automatically throw errors for HTTP response codes like 404 or 500. You need to handle such cases manually with response.ok.

fetch('https://api.example.com/data')
  .then((response) => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .catch((error) => console.error('Fetch error:', error));

Working with JSON Data

When using the Fetch API, the response must often be converted into JSON format using the .json() method. This step is necessary because the Fetch API returns a Response object, which contains metadata and the raw response body.

// Sample JSON data
{  
  "user": {  
    "id": 1,  
    "name": "John Doe",  
    "address": {  
      "city": "New York",  
      "zipcode": "10001"  
    }  
  }  
}  
//

useEffect(() => {  
  fetch('https://api.example.com/user')  
    .then((response) => {  
      if (!response.ok) {  
        throw new Error(`HTTP error! status: ${response.status}`);  
      }  
      return response.json();  
    })  
    .then((data) => {  
      console.log('User City:', data.user.address.city);  
    })  
    .catch((error) => console.error('Error fetching data:', error));  
}, []);  

Here, response.json() returns a promise that resolves to the parsed JSON data. You can then work with the data as needed.

Error Handling in Parsing

If the response isn’t valid JSON, calling .json() will throw an error. You need to catch this explicitly:

fetch('https://api.example.com/invalid-json')  
  .then((response) => response.json())  
  .catch((error) => {  
    console.error('Error parsing JSON:', error.message);  
  });  

Fetching Data Using Axios

What is Axios?

Axios is a popular third-party library for making HTTP requests. Unlike Fetch, it provides features such as interceptors, automatic JSON parsing, and better error handling out of the box.

Basic Example with Axios
Here’s the same example as above, rewritten with Axios:

import axios from 'axios';
import { useEffect, useState } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    axios.get('https://jsonplaceholder.typicode.com/users')
      .then((response) => setUsers(response.data))
      .catch((error) => console.error('Axios error:', error));
  }, []);

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
export default UserList;

Error Handling in Axios

Axios automatically throws an error for HTTP response codes that indicate failure. It also supports interceptors, allowing you to centralize your error-handling logic.

axios.interceptors.response.use(
  (response) => response,
  (error) => {
    console.error('Error:', error);
    return Promise.reject(error);
  }
);

Why Do We Need Axios? Key Advantages Over Fetch

  • Interceptors for request/response transformations.

  • Automatic JSON Parsing, saving time and code.

  • Timeout Configuration to avoid hanging requests.

  • Cancellation with CancelToken for better control.

  • Wider browser support when compared to fetch API.

When to Choose Axios

If your app requires features like request cancellation, request retries, or large-scale error handling, Axios is often better than Fetch. It also has a wider browser when compared to the fetch API.

Is Fetch API Alone Enough?

The answer is: Yes and No.

For small projects, Fetch API can suffice. However, for complex apps, its limitations—like lack of built-in interceptors, caching, and structured error handling, may become bottlenecks.

Caching and Validation

Caching and validation are critical for optimizing the performance of web applications. They help reduce redundant API calls, minimize latency, and give a better user experience.

Using React Query (TanStack Query)

React Query, part of the TanStack library, simplifies data fetching, caching, and server state synchronization. It automatically prefetches data in the background, ensuring your UI reflects the latest server data. Also, it simplifies the way you handle loading and error states.

Basic Example: Fetching Data with React Query

import { useQuery } from '@tanstack/react-query';

function Posts() {
  // Automatically manages loading, error, and data states.
  const { data, error, isLoading } = useQuery(['posts'], fetchPosts);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {data.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Automatic Caching

  • React Query caches responses by default. If you fetch the same data again, it retrieves the result from the cache, avoiding redundant network requests.

  • Example: Refetching after cache invalidation:


// Component 1: Initial Fetch
// Using useQuery to fetch posts with the key 'posts'.
const { data, error, isLoading } = useQuery(['posts'], fetchPosts);

// Component 2 Reusing the Same Query Key
// When another instance of useQuery with the same key 'posts' is mounted, 
// it retrieves data from the cache instead of calling the API again.
const { data, error, isLoading } = useQuery(['posts'], fetchPosts);
 
 // data is served from the cache
// and refetched in the background to update with the latest API values.
 
 const { refetch } = useQuery(['posts'], fetchPosts, { staleTime: 5000 });

// Refetching manually when needed
<button onClick={() => refetch()}>Refetch Posts</button>;


Pagination with React Query


const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
  ['paginatedPosts'],
  fetchPaginatedPosts,
  {
    getNextPageParam: (lastPage, pages) => {
      if (lastPage.length < 10) return undefined; // No more pages
      return pages.length + 1;
    },
  }
);

return (
  <div>
    {data.pages.map((page, i) => (
      <div key={i}>
        {page.map((post) => (
          <p key={post.id}>{post.title}</p>
        ))}
      </div>
    ))}
    {hasNextPage && <button onClick={fetchNextPage}>Load More</button>}
  </div>
);

Mutations for POST/PUT/DELETE

const mutation = useMutation((newPost) =>
  fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(newPost),
  })
);

const handleAddPost = () => {
  mutation.mutate({ title: 'New Post', body: 'Post content' });
};

return <button onClick={handleAddPost}>Add Post</button>;

Using SWR (Stale-While-Revalidate)

SWR (from Vercel) is another popular library for data fetching with caching capabilities.

Basic SWR Example

import useSWR from 'swr';

const fetcher = (url) => fetch(url).then((res) => res.json());
// It requires a fetcher function, and we can create our own fetcher using tools like the Fetch API, Axios, or GraphQL. In this example we created wrapper around fetch API.

function Posts() {
  const { data, error } = useSWR('https://jsonplaceholder.typicode.com/posts', fetcher);

  if (!data) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {data.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Focus and Revalidation

SWR automatically refetches data when the user revisits the tab or focuses back on the app:

useSWR('/api/user', fetcher, { revalidateOnFocus: true });

Optimistic Updates

Example of updating the UI optimistically before the server confirms the change:

const { mutate } = useSWR('/api/posts', fetcher);

const handleAddPost = async (newPost) => {
  mutate(
    (posts) => [...posts, newPost],
    false // Don't trigger a revalidation
  );
  await fetch('/api/posts', {
    method: 'POST',
    body: JSON.stringify(newPost),
  });
  mutate(); // Trigger revalidation
};

GraphQL for Data Fetching

What is GraphQL?

GraphQL is an API query language that provides more flexibility than REST by allowing you to request only the data you need.

Note: GraphQL isn’t just a client-side tool

Yes, it also requires server-side configuration. The server defines a GraphQL schema that describes the types of data and the operations (queries, mutations, subscriptions) clients can perform. This makes GraphQL a collaborative tool between frontend and backend developers, offering better efficiency and flexibility in fetching data.

Fetching Data with GraphQL in React

Use @apollo/client to fetch data efficiently:

import { gql, useQuery } from '@apollo/client';

const GET_USERS = gql`
  query {
    users {
      id
      name
    }
  }
`;

function UserList() {
  const { data, loading, error } = useQuery(GET_USERS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Conclusion

Fetching data is a cornerstone of React development. While the Fetch API is sufficient for basic needs, tools like Axios, React Query, and GraphQL enable you to build scalable, efficient applications. Choose the right approach based on your project’s complexity and requirements, and you’ll be well-equipped to handle any data-fetching challenges.

Join Ram on Peerlist!

Join amazing folks like Ram and thousands of other people in tech.

Create Profile

Join with Ram’s personal invite link.

0

6

0