Client-Side Fetching
Most list and detail pages should stay on the server. Client fetching shines when data depends on browser events: live search typing, infinite scroll, WebSocket dashboards, or third-party widgets that need window.
When the browser must fetch
Most list and detail pages should stay on the server. Client fetching shines when data depends on browser events: live search typing, infinite scroll, WebSocket dashboards, or third-party widgets that need window.
Real-life example: Server fetch is the hotel printing your room bill at checkout. Client fetch is refreshing the lobby screen every few seconds while guests walk by.
useEffect fetch pattern
Classic pattern: loading flag, error flag, cleanup so a slow response does not update an unmounted component.
"use client";
import { useEffect, useState } from "react";
type Tip = { id: string; text: string };
export function LiveTips() {
const [tips, setTips] = useState<Tip[]>([]);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
async function load() {
try {
const res = await fetch("/api/tips");
if (!res.ok) throw new Error("Failed to load tips");
const data = (await res.json()) as Tip[];
if (!cancelled) setTips(data);
} catch (err) {
if (!cancelled) {
setError(err instanceof Error ? err.message : "Unknown error");
}
}
}
load();
return () => {
cancelled = true;
};
}, []);
if (error) return <p role="alert">{error}</p>;
if (!tips.length) return <p>Loading tips…</p>;
return (
<ul>
{tips.map((t) => (
<li key={t.id}>{t.text}</li>
))}
</ul>
);
}SWR and React Query — brief mention
Libraries like SWR and TanStack Query add caching, revalidation, deduping, and retries so you do not hand-roll that in every useEffect.
Use them when you have many client-driven endpoints. For one simple poll, a small useEffect may be enough.