Use for client APIs, SWR hooks, cache invalidation, async errors, useEffect migration, home first paint and persistent caches.
复制下面这句话,粘贴给 Claude Code、Codex、Cursor 等 AI 编程工具,它会读取安装说明并在你确认后完成安装。
请阅读 https://ai.atlankj.com/install/asset/gh-data-fetching-architecture-e17c0388ed14 ,按照其中的说明把「data-fetching-architecture」安装到你(当前 AI 工具)中。执行前先告诉我将运行的命令和写入的位置,等我确认。
查看 AI 将读取的安装说明正在读取 GitHub 原文…
内容来自 GitHub 原始文件,由原作者维护。在 GitHub 查看
Component → Store useFetchXxx hook → Service → lambdaClient
← SWR request state + store data ← response
src/services/ own API calls: .query() for reads, .mutate() for writes.
Export a service instance per domain; components and stores do not call lambdaClient directly.useClientDataSWR and return its SWR response, including error
and mutate. Sync successful results into the store through the wrapper's supported
callback (onSuccess, or onData for the sync wrapper).useEffect or duplicate server data in component useState.useFetchXxx for read hooks and refreshXxx for cache invalidation.flattenActions, use zustand.When changing home/sidebar first paint or persisted display data, read
references/home-first-paint.md. It covers
avoiding flicker, reusing persistence, and the user displaySnapshot.ts boundary.
Reuse the domain key factory in src/libs/swr/keys.ts; define a shared key if the
domain has none. Include every parameter that changes the response: entity or parent
id, filters, sort, and pagination. Read and refresh must construct the same key.
Import useClientDataSWR and mutate from @/libs/swr, keeping the application's
workspace/cache handling. Return a null key when a required id is absent; passing
undefined to a store hook only disables fetching if that hook maps it to null.
For example, inside an action class:
useFetchBenchmarks = () =>
useClientDataSWR(evalKeys.benchmarks(), () => agentEvalService.listBenchmarks(), {
onSuccess: (data) => {
this.#set({ benchmarkList: data, benchmarkListInit: true });
},
});
refreshBenchmarks = async () => {
await mutate(evalKeys.benchmarks());
};
Read the current benchmark action for store wiring and the SWR wrappers for their actual options. These links locate implementation; they do not make every existing call site a template to copy unchanged.
Hooks that share a known parent id can run together. If one request needs a value
returned by another, keep its key null until that value exists. Do not assume the
application wrapper uses upstream SWR's default deduplication interval.
For lists cached separately under multiple parents, see parent-keyed lists. Ordinary flat lists need no extra layer.
finally. Use per-id state for
row updates/deletes so unrelated rows remain usable; create can use a separate flag
because no persistent id exists yet.zustand convention. Do not remove the
row optimistically or apply create/update's optimistic recipe to deletion.finally block clears pending state
but does not by itself recover an optimistic write.Consume the read hook's SWR response. Success-only flags such as isInit, missing
map entries, and data ?? [] cannot distinguish an initial request failure from
loading or empty results.
Use AsyncBoundary for standard loading/error/empty/data surfaces; use AsyncError
for custom layouts and inline failures. Pass the original SWR data to the boundary:
undefined means no successful result, whereas [] is a settled empty result.
const BenchmarkList = () => {
const useFetchBenchmarks = useEvalStore((s) => s.useFetchBenchmarks);
const benchmarks = useEvalStore((s) => s.benchmarkList);
const { data, error, isLoading, mutate } = useFetchBenchmarks();
return (
<AsyncBoundary
data={data}
empty={<EmptyState />}
error={error}
isEmpty={data?.length === 0}
isLoading={isLoading}
onRetry={() => {
void mutate();
}}
>
<BenchmarkCards items={benchmarks} />
</AsyncBoundary>
);
};
The example's EmptyState and BenchmarkCards stand for the surface's existing
renderers. Follow these distinctions when adapting it:
NotFound / zero-value defaults. Do not put
an error branch after if (!map[id]) return <Skeleton />; failure may never fill the map.loadMoreError and show an inline Retry row.
Suspend observer-triggered retries while that error is unresolved.Move the API call into its service and the request into a store SWR hook. Reuse the
existing state shape and action organization unless they need to change for the task.
Replace the component's effect/local fetch state with that hook and selectors, then
connect error, retry, and pending feedback. Refresh the same cache key after writes.
Check initial failure, retry, settled-empty results, background failure, and switching ids against the surface being changed. For stale data, compare read/refresh keys and the store bucket they update; for stuck loading, inspect rejected requests as well as success callbacks. Do not add a second fetch or another loading flag to mask the cause.