Render every data state
A loader passes data, loading and error; the caller renders each.
From frontend-patterns: “Render Props Pattern”.
Turn the network knob
Refetch, then tick Fail and refetch again.
Success branch onlyBad
fetch(url).then(r => r.json()).then(setMarkets)Every branchGood
if (loading) … if (error) … return data<DataLoader<Market[]> url="/api/markets">
{(markets, loading, error) => {
if (loading) return <Spinner />
if (error) return <Error error={error} />
return <MarketList markets={markets!} />
}}
</DataLoader>
Watch loading clear
Send one that succeeds, then one that fails.
Clears in
thenBad- loading
- error
- renders
.then(d => { setData(d); setLoading(false) }).catch(setError)Clears in
finallyGood- loading
- error
- renders
.then(setData).catch(setError).finally(() => setLoading(false))Same loader, two renders
Type to filter. One response feeds both views.
/api/markets
asTablechildrenasCardschildrenconst states = (render) => (markets, loading, error) => {
if (loading) return <Spinner />
if (error) return <Error error={error} />
if (!markets.length) return <Empty />
return render(markets)
}
const asTable = states(m => <MarketTable markets={m} />)
const asCards = states(m => <MarketCards markets={m} />)
Why it matters, pros and cons
Why it matters
Fetching lives in one place; the caller renders every state, never a blank.
Pros
- A missing branch is obvious in review.
finally()clears loading on both paths.- One loader feeds any number of views.
Cons
fetchresolves on 500; checkres.ok.- Reset state and drop stale responses when
urlchanges. - App Router: prefer Server Components with Suspense.