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

      asTablechildren
      asCardschildren

      const 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

      • fetch resolves on 500; check res.ok.
      • Reset state and drop stale responses when url changes.
      • App Router: prefer Server Components with Suspense.