Data-fetching components handle loading, success, error, and empty states
Why it matters
Components that only handle the success path leave users stranded in three common failure modes: slow network (indefinite spinner), API error (blank or partially rendered page), and empty server response (no indication of why nothing appeared). CWE-703 (improper check for exceptional conditions) applies to missing error state handling. In vibe-coded applications this is one of the most frequent defects: AI code generation reliably handles the success path and omits the loading, error, and empty branches — leaving large surface areas completely unhandled.
Severity rationale
Low individually, but high in aggregate — missing error and empty states across many data-fetching components produce a product that feels broken on any slow or unreliable connection.
Remediation
Handle all four states explicitly in every data-fetching component. With TanStack Query or SWR, the state variables are provided; with raw useEffect, model them explicitly.
// components/item-list.tsx
export function ItemList() {
const { data, isLoading, error } = useQuery({
queryKey: ['items'],
queryFn: fetchItems,
})
if (isLoading) return <Skeleton />
if (error) return <ErrorMessage message="Failed to load items. Please refresh." />
if (!data?.length) return <EmptyState message="No items yet." />
return <ul>{data.map((item) => <li key={item.id}>{item.name}</li>)}</ul>
}
Do not conflate the error state with the empty state — an empty array is not an error, and an error is not an empty array.
Detection
-
ID:
data-fetching-states -
Severity:
low -
What to look for: Count all data fetching hooks and components (useQuery, useSWR, fetch in useEffect). Enumerate which handle all 3 states (loading, error, success) vs. which only handle success. Examine components that fetch data (useEffect with fetch, async/await, React Query, SWR, etc.). Check whether they explicitly handle four states: loading (show spinner), success (show data), error (show error message), and empty (show empty state).
-
Pass criteria: Data-fetching components explicitly handle all four states: loading, success, error, and empty. At least 90% of data fetching components must explicitly handle loading, error, and empty states.
-
Fail criteria: Components lack explicit error state handling, or don't show empty state, or show loading indefinitely.
-
Skip (N/A) when: The application has no data-fetching components.
-
Do NOT pass when: Components handle error state by showing a loading spinner indefinitely instead of an actual error message.
-
Cross-reference: For graceful API failure, see
graceful-api-failure. -
Detail on fail:
"Data-fetching components lack error state handling. Failed requests show blank screen"or"Loading state shown but no empty-state when API returns empty array" -
Remediation: Handle all four states:
// components/user-list.tsx — all 3 data states if (isLoading) return <Skeleton /> if (error) return <ErrorMessage message="Failed to load users" /> if (!data.length) return <EmptyState message="No users found" /> return <UserTable data={data} />export function ItemsList() { const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { fetch('/api/items') .then(r => r.json()) .then(data => { setItems(data) setError(null) }) .catch(err => { setError(err) setItems([]) }) .finally(() => setLoading(false)) }, []) if (loading) return <div>Loading...</div> if (error) return <div>Error: {error.message}</div> if (items.length === 0) return <div>No items found</div> return ( <ul> {items.map(item => <li key={item.id}>{item.name}</li>)} </ul> ) }
External references
- cwe · CWE-703 — Improper Check or Handling of Exceptional Conditions
- iso-25010:2011 · reliability.fault-tolerance
Taxons
History
- 2026-04-18·v1.0.0·Initial import from error-resilience·automated