Careeroza
Real World Example
Typed Data Fetching Hook
Dashboards (Razorpay Dashboard, Vercel, Notion) fetch data in many components. A generic hook returns a discriminated union state, so components must handle loading and error before touching the data.
"use client";
import { useEffect, useState } from "react";
type AsyncState<T> =
| { status: "loading" }
| { status: "error"; error: string }
| { status: "success"; data: T };
export function useFetch<T>(url: string, parse: (json: unknown) => T): AsyncState<T> {
const [state, setState] = useState<AsyncState<T>>({ status: "loading" });
useEffect(() => {
const controller = new AbortController();
setState({ status: "loading" });
fetch(url, { signal: controller.signal })
.then((res) => (res.ok ? res.json() : Promise.reject(new Error("HTTP " + res.status))))
.then((json: unknown) => setState({ status: "success", data: parse(json) }))
.catch((e: unknown) => {
if (!controller.signal.aborted) setState({ status: "error", error: e instanceof Error ? e.message : "Unknown error" });
});
return () => controller.abort();
}, [url, parse]);
return state;
}
type Order = { id: string; total: number; status: "PLACED" | "SHIPPED" | "DELIVERED" };
const parseOrders = (json: unknown) => json as Order[]; // use a Zod schema in real code
export function RecentOrders() {
const state = useFetch("/api/orders?limit=5", parseOrders);
if (state.status === "loading") return <p>Loading orders...</p>;
if (state.status === "error") return <p role="alert">Could not load orders: {state.error}</p>;
return (
<ul>
{state.data.map((o) => (
<li key={o.id}>{o.id} - Rs. {o.total} - {o.status}</li>
))}
</ul>
);
}