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 };
}
How It Works:
-
State Management:
-
data
: Stores the fetcheddata
. -
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.
- This function uses the
-
Type Safety with Generics:
- By passing
<T>
as a generic, we allow the hook to work with any type of data (array, object, etc.).
- By passing
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;
Key Points:
- Fetching Data on Component Mount:
- We’re using the
useEffect
hook to callfetchData
when the component mounts.
- We’re using the
- 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.
- Once the data is fetched, it’s displayed in a
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>
);
};
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)