Hook for fetching data using only React
Using React hooks for fetching and controlling the state of data. Data, loading and error handling.
Simplifying data fetching in React with a custom hook
Fetching data in React can sometimes feel repetitive, especially if you’re juggling multiple states for data, loading, and error handling. To streamline this, let me introduce a custom hook that makes data fetching clean, reusable, and easy to manage.
Here’s how it works, step-by-step:
The idea behind the hook
React’s functional components shine when you can break out reusable logic into hooks. Instead of writing the same useState
and useEffect
boilerplate in multiple components, this hook bundles it all up nicely.
With useFetch
, you get:
- data state: Where your fetched data lives.
- loading state: To indicate the request’s progress.
- error state: For handling errors when things don’t go smoothly.
- refetch functionality: Fetch fresh data whenever you need it.
The code: useFetch
import { useState, useEffect } from "react";
type FetchOptions = RequestInit;
type UseFetchResult<T> = {
data: T | null;
error: string | null;
loading: boolean;
refetch: () => void;
};
export function useFetch<T>(url: string, options: FetchOptions = {}): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [reload, setReload] = useState<number>(0); // Used to trigger refetch
const refetch = () => setReload((prev) => prev + 1);
useEffect(() => {
let isMounted = true;
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const result = await response.json();
if (isMounted) {
setData(result);
}
} catch (err) {
if (isMounted) {
setError((err as Error).message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchData();
return () => {
isMounted = false;
};
}, [url, options, reload]);
return { data, error, loading, refetch };
}
How it works
-
states:
data
stores the fetched data.loading
indicates whether a request is in progress.error
captures error messages if the fetch fails.reload
triggers re-fetching whenrefetch
is called.
-
refetch
function:
Callingrefetch
bumps thereload
counter, which forces theuseEffect
to run again. -
cleanup with
isMounted
:
To avoid updating state after the component unmounts (and prevent those annoying React warnings), we track whether the component is still mounted.
How to use it
This hook is flexible and can be used in any functional component:
import { useFetch } from "./useFetch";
function App() {
const { data, error, loading, refetch } = useFetch(
"https://api.example.com/data"
);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h1>Fetched Data</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
<button onClick={refetch}>Refetch</button>
</div>
);
}