Agent Skills › getsentry/sentry › frontend-data-fetching

frontend-data-fetching

GitHub

指导在 Sentry 前端使用 TanStack Query 和 apiOptions 进行数据获取,涵盖查询、缓存及类型推断规范。

.agents/skills/frontend-data-fetching/SKILL.md getsentry/sentry

Trigger Scenarios

fetch data add an API call useQuery useMutation apiOptions queryFn pagination headers X-Hits why is my query type wrong

Install

npx skills add getsentry/sentry --skill frontend-data-fetching -g -y
More Options

Non-standard path

npx skills add https://github.com/getsentry/sentry/tree/master/.agents/skills/frontend-data-fetching -g -y

Use without installing

npx skills use getsentry/sentry@frontend-data-fetching

指定 Agent (Claude Code)

npx skills add getsentry/sentry --skill frontend-data-fetching -a claude-code -g -y

安装 repo 全部 skill

npx skills add getsentry/sentry --all -g -y

预览 repo 内 skill

npx skills add getsentry/sentry --list

SKILL.md

Frontmatter
{
    "name": "frontend-data-fetching",
    "description": "Fetch data in Sentry's frontend with TanStack Query and apiOptions. Use when adding or editing React code in static\/ that calls the API — useQuery\/useMutation\/useInfiniteQuery, apiOptions, queryOptions\/mutationOptions, fetchMutation, reading response headers\/pagination, or conditional fetching. Trigger on \"fetch data\", \"add an API call\", \"useQuery\", \"useMutation\", \"apiOptions\", \"queryFn\", \"pagination headers\", \"X-Hits\", or \"why is my query type wrong\"."
}

Frontend Data Fetching (TanStack Query + apiOptions)

Use apiOptions with useQuery from TanStack Query. Do not use useApiQuery, getApiQueryData, or setApiQueryData — they are deprecated.

import {skipToken, useQuery} from '@tanstack/react-query';
import {apiOptions} from 'sentry/utils/api/apiOptions';

// Basic usage
const query = useQuery(
  apiOptions.as<ResponseType>()('/organizations/$organizationIdOrSlug/endpoint/', {
    path: {organizationIdOrSlug: organization.slug},
    staleTime: 30_000,
  })
);

// Conditional fetching — pass skipToken as path to disable the query
const query = useQuery(
  apiOptions.as<ResponseType>()('/organizations/$organizationIdOrSlug/items/$itemId/', {
    path: itemId ? {organizationIdOrSlug: organization.slug, itemId} : skipToken,
    staleTime: 30_000,
  })
);

Key rules:

  • staleTime is required — you must choose a value (0, a number in ms, Infinity, or 'static').
  • Build abstractions over apiOptions, not over useQuery. Return the options object so consumers can pass it to useQuery, useQueries, prefetchQuery, etc.
  • Cache stores {json, headers}, not just the body. apiOptions uses select to extract .json by default, but getQueryData, setQueryData, retry functions, and predicate callbacks all receive the raw ApiResponse<T> shape.
  • never use api.requestPromise for a Query - it returns the wrong structure. If you must make a manual queryFn, use apiFetch.

TanStack Query Type Inference — NEVER Pass Call-Site Generics

CRITICAL: Never pass type parameters to useQuery, useMutation, mutationOptions, queryOptions, or any TanStack Query function at the call site. Let TypeScript infer types from your queryFn/mutationFn and callbacks. Passing call-site generics defeats inference, hides bugs, and creates maintenance burden.

// ❌ NEVER pass generics to useQuery, useMutation, mutationOptions, etc.
useMutation<ResponseType, RequestError, Variables, Context>({...})
mutationOptions<ResponseType, RequestError, Variables, Context>({...})
useQuery<ResponseType, RequestError>({...})

// ✅ Let types be inferred — annotate the mutationFn/queryFn instead
useMutation({
  mutationFn: (variables: MyVariables) =>
    fetchMutation<MyResponse>({...}),
})

Specific rules:

  1. Type the mutationFn parameters, not the hook/function generics. The variables type flows from the mutationFn signature.
  2. Use fetchMutation<T> to type the return value — the generic on fetchMutation is correct because it types the API response.
  3. Never type the error generic as RequestError — that's a type assertion in disguise. The error is Error by default. Use runtime narrowing (if (error instanceof RequestError)) when you need RequestError-specific properties.
  4. Never explicitly type the context — it is inferred from what onMutate returns. Creating a separate type FooContext = {...} and passing it as a generic is unnecessary.
  5. Same rule applies to queries — useQuery, queryOptions, useInfiniteQuery, etc. Types flow from queryFn and select.
// ❌ Explicit context type + error assertion
type MyContext = {previousData: Item[]};

mutationOptions<Item, RequestError, UpdateItemVars, MyContext>({
  mutationFn: variables => fetchMutation({...}),
  onMutate: async () => {
    const previousData = queryClient.getQueryData(itemQueryOptions);
    return {previousData};
  },
  onError: (_error, _variables, context) => {
    queryClient.setQueryData(key, context?.previousData);
  },
})

// ✅ Everything is inferred
mutationOptions({
  mutationFn: (variables: UpdateItemVars) =>
    fetchMutation<Item>({...}),
  onMutate: async () => {
    const previousData = queryClient.getQueryData(itemQueryOptions);
    return {previousData};
  },
  onError: (_error, _variables, context) => {
    // context type is inferred from onMutate return
    queryClient.setQueryData(key, context?.previousData);
  },
})

Accessing response headers (pagination, hit counts)

By default, apiOptions selects only the JSON body from the response. If you need response headers (e.g., Link for pagination or X-Hits / X-Max-Hits for total counts), override select with selectJsonWithHeaders:

import {useQuery} from '@tanstack/react-query';
import {apiOptions, selectJsonWithHeaders} from 'sentry/utils/api/apiOptions';

const {data} = useQuery({
  ...apiOptions.as<Item[]>()('/organizations/$organizationIdOrSlug/items/', {
    path: {organizationIdOrSlug: organization.slug},
    query: {cursor, per_page: 25},
    staleTime: 0,
  }),
  select: selectJsonWithHeaders,
});

// data is ApiResponse<Item[]> — an object with `json` and `headers`
const items = data?.json ?? [];
const pageLinks = data?.headers.Link; // string | undefined
const totalHits = data?.headers['X-Hits']; // number | undefined
const maxHits = data?.headers['X-Max-Hits']; // number | undefined

Note that X-Hits and X-Max-Hits are already parsed to number | undefined — no parseInt needed.

Version History

  • d3c9056 Current 2026-08-20 20:32

Same Skill Collection

.agents/skills/bump-sentry-dependency/SKILL.md
.agents/skills/cmdk-actions/SKILL.md
.agents/skills/design-system/SKILL.md
.agents/skills/feature-flags/SKILL.md
.agents/skills/generate-frontend-forms/SKILL.md
.agents/skills/generate-migration/SKILL.md
.agents/skills/generate-snapshot-tests/SKILL.md
.agents/skills/hybrid-cloud-rpc/SKILL.md
.agents/skills/hybrid-cloud-test-gen/SKILL.md
.agents/skills/lint-fix/SKILL.md
.agents/skills/lint-new/SKILL.md
.agents/skills/migrate-container-queries/SKILL.md
.agents/skills/migrate-frontend-forms/SKILL.md
.agents/skills/notification-platform/SKILL.md
.agents/skills/react-component-documentation/SKILL.md
.agents/skills/react-testing/SKILL.md
.agents/skills/scraps-review/SKILL.md
.agents/skills/seer-embed/SKILL.md
.agents/skills/sentry-backend-bugs/SKILL.md
.agents/skills/sentry-javascript-bugs/SKILL.md
.agents/skills/sentry-security/SKILL.md
.agents/skills/analytics/SKILL.md
.agents/skills/backend-conventions/SKILL.md
.agents/skills/cell-architecture/SKILL.md
.agents/skills/django-models/SKILL.md
.agents/skills/hybrid-cloud-outboxes/SKILL.md
.agents/skills/migrate-breadcrumb-list/SKILL.md
.agents/skills/remove-option-or-flag/SKILL.md
.agents/skills/setup-dev/SKILL.md

Metadata

Files
0
Version
991ee88
Hash
7f2a3d1d
Indexed
2026-08-20 20:32

inicio - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-10-04 07:33
浙ICP备14020137号-1