DEV Community

Cover image for Mastering React Data Fetching: A Comprehensive Guide for Developers 🚀
Maitrish Mukherjee
Maitrish Mukherjee

Posted on

Mastering React Data Fetching: A Comprehensive Guide for Developers 🚀

Hey, fellow React devs!

I am hoping this will be yet another all-encompassing article about React data fetching techniques. This will give you a SOLID understanding of data fetching in React.

Trust me, this is the extent of data fetching. Yes, there are a lot of other use-case-specific data fetching that could happen (like fetching paginated data with proper type-casting for a table component, or sequential promises, etc.), but this is the extent. What I mean is there are an infinite number of add-ons that could be used ON TOP of these.

In this comprehensive guide, we'll start from the basics, gradually progressing to advanced techniques, and finally, we'll architect React apps with a focus on separating concerns using React Query. Whether you're just starting or looking to level up your skills, this blog will equip you with the knowledge to architect React applications like a pro.

1. The Fundamentals: Fetch and Axios in useEffect

1.1 Basics with fetch:
Let's kick off with the fundamental approach using the fetch API in a functional component. We'll explore how to fetch data and update the component's state using the useState and useEffect hooks.

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

const BasicFetchExample = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      {data ? (
        // Render data
      ) : (
        // Render loading state or error message
      )}
    </div>
  );
};

export default BasicFetchExample;
Enter fullscreen mode Exit fullscreen mode

1.2 Axios for Enhanced Functionality:
Building upon the basics, let's explore Axios, a popular HTTP client, to handle data fetching. Axios simplifies error handling and provides additional features like request/response interceptors.

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

const AxiosExample = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await axios.get('https://api.example.com/data');
        setData(response.data);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      {data ? (
        // Render data
      ) : (
        // Render loading state or error message
      )}
    </div>
  );
};

export default AxiosExample;
Enter fullscreen mode Exit fullscreen mode

2. Advanced Separation of Concerns: Custom Hooks and HOCs

2.1 Custom Hooks for Modularity:
Now, let's introduce the concept of custom hooks. We'll create a reusable data fetching hook, encapsulating the logic for fetching and handling data.

// useDataFetching.js

import { useState, useEffect } from 'react';

const useDataFetching = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
};

export default useDataFetching;
Enter fullscreen mode Exit fullscreen mode

Now, we can easily use this custom hook in any component:

import React from 'react';
import useDataFetching from './useDataFetching';

const CustomHookExample = () => {
  const { data, loading, error } = useDataFetching('https://api.example.com/data');

  return (
    <div>
      {loading && <p>Loading...</p>}
      {error && <p>Error: {error}</p>}
      {data && (
        // Render data
      )}
    </div>
  );
};

export default CustomHookExample;
Enter fullscreen mode Exit fullscreen mode

2.2 Higher-Order Components (HOCs) for Reusability:
Another powerful technique for separating concerns is using Higher-Order Components. Let's create a data fetching HOC.

// withDataFetching.js

import React from 'react';

const withDataFetching = (url) => (WrappedComponent) => {
  return (props) => {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
      const fetchData = async () => {
        try {
          const response = await fetch(url);
          const result = await response.json();
          setData(result);
        } catch (error) {
          setError(error.message);
        } finally {
          setLoading(false);
        }
      };

      fetchData();
    }, [url]);

    return <WrappedComponent data={data} loading={loading} error={error} {...props} />;
  };
};

export default withDataFetching;
Enter fullscreen mode Exit fullscreen mode

Now, we can enhance any component with data fetching capabilities:

import React from 'react';
import withDataFetching from './withDataFetching';

const HOCExample = ({ data, loading, error }) => (
  <div>
    {loading && <p>Loading...</p>}
    {