From frontend-toolkit
Handle async data the house way — treat server state as a cache (not useState + useEffect), render loading/error/empty/success explicitly, fetch with keys/dedup/revalidation, and mutate optimistically then reconcile. Use when fetching data in a component, adding caching, wiring loading/error states, building a mutation, or reviewing data-fetching code.
How this skill is triggered — by the user, by Claude, or both
Slash command
/frontend-toolkit:data-fetchingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Stack-agnostic principles, concrete examples in React 19 and React Native.
Stack-agnostic principles, concrete examples in React 19 and React Native. The patterns port to Vue/Svelte and other query libraries; the judgments don't change.
Version targets: React 19, React Native 0.81 (current stable) / Expo SDK 54,
TanStack Query v5. Snippets grounded against Context7 (/reactjs/react.dev,
/tanstack/query, /facebook/react-native) at authoring time — see references/snippets.md.
Every data-fetching decision reduces to: is this server state or client state?
Server state lives on a server, is fetched asynchronously, can be shared, and goes
stale — it's a cache you borrow, not state you own. Treat it as a cache (dedup,
revalidate, expire) and most loading/error/staleness bugs disappear. Genuine client
state (form input, which tab is open) is the only thing you truly own with useState.
useState. The useEffect-fetch-into-useState
pattern re-implements caching badly: no dedup, no revalidation, request races, stale
closures, a refetch on every mount. Reach for a cache — TanStack Query / SWR, or
React 19 use() + Suspense — that owns fetching, caching, and refetching.data is present
crashes on the first slow network or empty result. Make the states explicit — a
discriminated result, or Suspense + an error boundary.['user', id]). Same key → shared, deduped, cached. Show cached data
instantly and revalidate in the background; set staleTime so you don't refetch
what's still fresh.if (loading) ladders. React 19's
use() reads a promise and suspends; a <Suspense> ancestor renders the fallback and
an error boundary catches rejection. One place for loading, one for errors — not
ternaries scattered through every component.useOptimistic / a form action, or a cache write via onMutate), confirm
against the server, and roll back on failure. Invalidate the affected queries so the
cache re-syncs with the truth.component-design "separate layout from data" rule). Don't
prop-drill server state through five layers — read it from the cache where it's used.AppState) and on
reconnect (NetInfo), there's no SSR, and every request needs a timeout/abort because
mobile networks drop silently. See references/pitfalls.md.references/checklist.md against the read or mutation you're writing or reviewing.references/snippets.md (React 19 use() + Suspense,
TanStack Query read, optimistic mutation, React Native wiring).references/pitfalls.md before you ship — the useEffect fetch and the
missing error state catch almost everyone.state-managementcomponent-designforms-and-validationfrontend-testingfrontend-performancenpx claudepluginhub ravnhq/sasso-hq --plugin frontend-toolkitMines projects and conversations into a searchable memory palace. Activates on queries about MemPalace, memory palace, mining, searching, palace setup, wings, rooms, drawers, or recalling past work.
Whole-repo audit for over-engineering: finds dead code, unnecessary abstractions, stdlib-replaceable dependencies. Outputs ranked findings and net line/dep savings.