# useFetch > Declarative fetch with loading and error status. - Category: Data - Package: `hookli` (install with `npm i hookli`) - Docs: https://hookli.vercel.app/docs/use-fetch ## Signature ```ts useFetch(url: string): { data: T | null; loading: boolean; error: Error | null } ``` ## Parameters - `url`: `string` — The endpoint to GET. The request starts on mount and re-runs whenever the url changes. ## Returns - `data`: `T | null` — The parsed JSON body; null until the first request succeeds. Kept from the previous url while a refetch is in flight. - `error`: `Error | null` — Set on network failure or a non-ok response ("HTTP error! status: 404"). Never cleared by later requests — remount the component (e.g. key={url}) for fresh state. - `loading`: `boolean` — True until the first request settles. Not reset to true when the url changes — remount for a per-request loading flag. ## Usage ```tsx import { useFetch } from "hookli"; type Post = { id: number; title: string; body: string }; export function Demo() { const { data, loading, error } = useFetch( "https://jsonplaceholder.typicode.com/posts/1", ); if (loading) return

Loading…

; if (error) return

Request failed: {error.message}

; return
{data?.title}
; } ``` ## Source `src/hooks/useFetch.hook.ts` ```ts import { useEffect, useState } from "react"; interface UseFetchResponse { data: T | null; error: Error | null; loading: boolean; } export const useFetch = (url: string): UseFetchResponse => { const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const fetchData = async () => { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const fetchedData = await response.json(); setData(fetchedData); } catch (err) { setError(err as Error); } finally { setLoading(false); } }; fetchData(); }, [url]); return { data, error, loading }; }; ```