DEV Community

Cover image for Simplifying HTTP Requests in React with Custom Hooks 🎣
Ayo Ashiru
Ayo Ashiru

Posted on

Simplifying HTTP Requests in React with Custom Hooks 🎣

When building React applications, dealing with HTTP requests is inevitable. A common challenge developers face is managing the complexity of making API calls, handling loading states, errors, and displaying fetched data efficiently.

This article is going to cover how to simplify HTTP requests using custom hooks in React. We’ll cover:

  • Creating a reusable custom hook

  • Managing loading, error, and data states

  • Implementing the hook in a React component

By the end, you’ll have a cleaner and more reusable approach for handling data fetching in your applications!

Why Custom Hooks?

Custom hooks allow you to encapsulate logic that can be reused across different components. Instead of cluttering each component with API calls, state management, and error handling, we can centralize that logic inside a hook.

This separation leads to:

  • Cleaner components

  • Easier maintenance

  • Reusability across multiple components

Here's What We’ll Build

We’re going to create a useData custom hook that can fetch data from any API, manage loading and error states, and then display that data in a React component.

Step 1: Creating the useData Hook

Let’s start by creating the hook. We’ll use the useState and useCallback hooks from React to manage data, loading, and errors.

import { useState, useCallback } from 'react';

export function useData<T>() {
  const [data, setData] = useState<T | null>(null);
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);

  const fetchData = useCallback(async (url: string) => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error('Network response was not ok');
      const result: T = await response.json();
      setData(result);
    } catch (error) {
      setError(
        error instanceof Error ? error.message : 'An unknown error occurred'
      );
    } finally {
      setIsLoading(false);
    }
  }, []);

  return { data, isLoading, error, fetchData };
}
Enter fullscreen mode Exit fullscreen mode

How It Works:

  • State Management:

    • data: Stores the fetched data.
    • isLoading: Tracks whether the request is in progress.
    • error: Stores any error messages if something goes wrong.
  • fetchData Function:

    • This function uses the fetch API to make the HTTP request.
    • It handles successful responses and errors by updating the relevant state variables.
  • Type Safety with Generics:

    • By passing <T> as a generic, we allow the hook to work with any type of data (array, object, etc.).

Step 2: Using the Hook in a Component

Now that we have the useData hook, let’s integrate it into a React component. This component will call our hook to fetch data from a sample API and handle the loading and error states.

import React, { useEffect } from 'react';
import { useData } from './useData'; // Import the custom hook

const DataFetcher: React.FC = () => {
  const { data, isLoading, error, fetchData } = useData<any>();

  useEffect(() => {
    fetchData('https://api.example.com/data');
  }, [fetchData]);

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

  return (
    <div>
      <h2>Data Fetched:</h2>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};

export default DataFetcher;
Enter fullscreen mode Exit fullscreen mode

Key Points:

  • Fetching Data on Component Mount:
    • We’re using the useEffect hook to call fetchData when the component mounts.
  • Loading and Error Handling:
    • While the data is being fetched, a loading message is shown. If an error occurs, an error message is displayed.
  • Rendering Data:
    • Once the data is fetched, it’s displayed in a pre tag to maintain formatting.

Step 3: Enhancing the User Experience

Let’s make the UI a bit more user-friendly. We’ll add some basic styles and enhance the user experience by disabling the submit button when the form is loading.

Here’s an example of how the final component would look:

const DataFetcher: React.FC = () => {
  const { data, isLoading, error, fetchData } = useData<any>();

  useEffect(() => {
    fetchData('https://api.example.com/data');
  }, [fetchData]);

  return (
    <div className="data-fetcher-container">
      <h1>Fetch Data Example</h1>

      {isLoading && <p>Loading data...</p>}
      {error && <p style={{ color: 'red' }}>Error: {error}</p>}

      {!isLoading && data && (
        <div>
          <h2>Data:</h2>
          <pre>{JSON.stringify(data, null, 2)}</pre>
        </div>
      )}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Why This Approach?

  • Separation of Concerns: The useData hook abstracts away the HTTP request logic, keeping your components clean and focused on rendering.

  • Reusability: Since the hook is generic, you can use it to fetch different types of data across your application.

  • Improved UX: The hook manages loading and error states efficiently, ensuring that users have feedback throughout the data fetching process.

Key Takeaways

  • Custom Hooks are a great way to encapsulate logic and keep your components clean.
  • Handling HTTP requests in React becomes much simpler when using hooks like useData.
  • The fetch API provides a powerful and flexible way to handle HTTP requests in JavaScript.

By adopting this pattern, your components will stay focused on rendering logic while the custom hook handles data fetching, loading, and error handling.

Want more insights? Follow me here for tips on React, TypeScript, and modern web development practices! 👨‍💻

Top comments (0)