Introduction
React has revolutionised the way we build web apps with its component-based architecture and declarative style. As your projects grow, it’s important to abstract logic into reusable, composable pieces—and that’s where custom hooks shine.
There are numerous questions about React hooks in interviews. One of the most frequently asked interview questions for React developers is:
“How do you create a custom hook for fetching data?”
In this blog, we’ll dive into building a useFetch hook from scratch and understand its benefits.
What Are Custom Hooks?
Custom hooks in React let you reuse logic across components. They start with use and allow you to encapsulate behaviour using built-in hooks like useState, useEffect, etc.
useFetch Hook
Let’s jump into the implementation:
import { useEffect, useState } from 'react';
const useFetch = (url) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
const fetchData = async () => {
try {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
setError(error);
}
} finally {
setLoading(false);
}
};
fetchData();
return () => {
controller.abort();
};
}, [url]);
return { loading, data, error };
};
export default useFetch;
Now, let's understand the code step by step:
1. State Management
We declare three pieces of state using useState:
-
data
: The API response data -
error
: Any error that occurs during the fetch call -
loading
: A boolean to represent the loading state
2. Effect Hook for Fetching
useEffect
is triggered when the url changes. This ensures our hook refetches data when needed.
3. AbortController
This is one of the interesting things. We use AbortController to cancel the fetch request if the component unmounts. When an API call is in progress, if the user unmounts the component, the AbortController will cancel the API call. It helps to avoid memory leaks and unnecessary state updates.
const controller = new AbortController();
const signal = controller.signal;
The signal is passed to the fetch
API. In case the component unmounts, the fetch
is aborted cleanly:
return () => {
controller.abort();
};
4. Error Handling
We check if the fetch
response is OK
; otherwise, we throw an error:
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
If the error is due to an abort, we skip setting the state. For other errors, we store them in the error.
5. Return Values
The hook returns { loading, data, error }
, making it easy to consume in any component.
Example Usage
Here’s how you can use the useFetch hook in a component:
import React from 'react';
import useFetch from './useFetch';
const UsersList = () => {
const { loading, data, error } = useFetch('https://jsonplaceholder.typicode.com/users');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
export default UsersList;
Conclusion
The useFetch hook is a simple yet powerful abstraction that encapsulates async behaviour and makes our components cleaner and more focused. Whether you’re preparing for interviews or building robust frontends, knowing how to craft such hooks is essential. If you liked this blog and want to learn more about Frontend Development and Software Engineering, you can follow me on Dev.
Top comments (0)