본문으로 건너뛰기
cccskills
무료GitHub에 공개

tanstack-query

Operately frontend data fetching with TanStack Query. Use when adding or changing page loaders, model hooks, Api.* calls, mutations, useLoadedData, Pages.useRefresh, or any web UI backend request. New code must use TanStack. When fixing or extending an existing surface, migrate that surface's API calls to TanStack in the same change.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(2)

  • SKILL.md7.0 KB
  • reference.md6.7 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

TanStack Query

All new web-app backend requests go through TanStack Query. Imperative Api.foo.bar() in loaders, generated tuple hooks (Api.foo.useBar()), and Pages.useRefresh() are the old pattern.

When you add a feature or fix on an existing page, hook, or model module, migrate that surface's queries and mutations to TanStack in the same change. Do not leave a mixed loader (one TanStack query plus one raw Api.* fetch) on the file you just edited.

For copy-paste skeletons and old→new mappings, see reference.md.

When to migrate

SituationDo
New page, loader, or mutationTanStack from the start
Feature or fix on an existing page/moduleMigrate that surface's API calls too
Typeahead / search-as-you-type (People.usePeopleSearch, Api.*.search as a search fn)Leave imperative
Unrelated sibling pageDo not expand the PR
ProjectPageOnly when that page is the requested work

ProjectPage is the last project-page migration. A one-line copy fix there does not require rewriting its loader.

How it works

Generated helpers live next to each endpoint in app/assets/js/api/index.tsx:

HelperRole
fooQuery(input)Prefetch in the router loader (staleTime: Infinity)
fooQueryOptions(input)queryKey + queryFn for useQuery / useLoadedQuery
fooQueryKey(input)Invalidate one cached input
fooQueryKeyPrefix()Invalidate every cached input for that endpoint
fooMutationOptions()mutationFn for useMutation

The shared client is app/assets/js/api/queryClient.ts.

flowchart LR
  loader["router loader: fooQuery"] --> cache["TanStack cache"]
  cache --> hook["useLoadedQuery / useQuery"]
  mutate["mutateAsync"] --> invalidate["invalidateQueries"]
  invalidate --> cache

Page loaders

Router loader prefetches and returns inputs, not payload:

  1. Build queryInput (same shape the API already used).
  2. await Api.namespace.fooQuery(queryInput) (parallelize with Promise.all).
  3. return { queryInput }.
  4. useLoadedData reads Pages.useLoadedData(), then useLoadedQuery(Api.namespace.fooQueryOptions(queryInput)).
  5. Check the declared types: do not assert fields that are already non-nullable. For nullable data, prefer safe defaults (for example, items ?? [] or permissions?.canCreateSpace ?? false), or hide optional UI when valid. Use assertPresent(value, message) only for data that may be absent but is essential and has no safe fallback, rather than an inline null check that throws.

Use useLoadedQuery, not useQuery, when the loader prefetched. It uses loaderBackedQueryOptions so the page does not refetch on mount unless the query was invalidated.

Replace Pages.useRefresh() with a local useRefresh that invalidateQueries on the page's query keys.

Canonical: ProjectPausePage/loader.tsx, ProjectDiscussionPage/loader.tsx.

Optional queries (URL may omit space/goal, or a parent fetch may fail): always call useLoadedQuery, pass enabled: input != null, and fall back in JS. See reference.md.

Mutations

Put wrappers in app/assets/js/models/<resource>/<resource>Lifecycle.ts (or projectDiscussionLifecycle.ts when the resource already has a sibling file).

export function useCreateProjectDiscussion() {
  const queryClient = useQueryClient();

  return useMutation({
    ...Api.projects.createDiscussionMutationOptions(),
    onSuccess: () => {
      void invalidateProjectDiscussionQueries(queryClient);
    },
  });
}

Pages call mutateAsync. Invalidate with *QueryKeyPrefix() so every cached input for that endpoint refreshes. Re-export from the model's index.tsx.

Do not switch every remaining call site of a generated tuple hook when you add a lifecycle wrapper. Update the surface you are on; leave others (for example WorkMap's Api.projects.useCreate()) until that file is migrated.

Canonical: projectDiscussionLifecycle.ts, projectLifecycle.ts.

Layout and non-prefetched queries

If the query is not prefetched in a router loader (company layout getMe), wrap with useQuery(fooQueryOptions(input)), not useLoadedQuery.

Canonical: models/people/index.tsx useGetMe.

Tests

Colocate Jest next to the lifecycle file (fooLifecycle.test.ts). Seed queryClient.setQueryData(key, {}), run the invalidate helper, assert getQueryState(key)?.isInvalidated. Cover the intended prefixes and one unrelated key that must stay clean.

Run make test FILE=assets/js/models/.../fooLifecycle.test.ts.

Existing feature tests for the page are the behavior net; run the ones that visit the migrated route.

Do not

  • Prefetch with raw Api.foo.bar(input) or Projects.getProject(...).
  • Return fetched records from the loader (return { project }). Return inputs.
  • Use Pages.useRefresh() after a TanStack loader — it will not update cache.
  • Introduce PageCache.fetch on new work.
  • Extract a shared loader helper for two similar pages unless duplication is already painful. Include flags and parent APIs usually differ.
  • Use ! to bypass missing query data, or assertPresent for non-nullable fields or data with a safe fallback.

Navigation and hover preloading

  • pageRoute runs route.loader for navigation: authentication, progress, synchronous onNavigate, then the page loader. Hover/focus runs only handle.dataLoader after 150 ms; the shared company loader stays navigation-only.
  • Keep page loaders read-only and reuse the same generated query inputs/options in useLoadedQuery. Use emptyLoader when no data is needed. Put navigation-only effects in synchronous onNavigate; never change headers in a preloadable loader.
  • auth defaults to true. Set preload: false for mutating reads, redirects with browser effects, context changes, and mandatory fresh checks. Do not relax freshness or company isolation for preloading. Individual links can opt out with data-preload="false"; cross-company links are skipped automatically.
  • Cached-query transport has no toast/reload effects. Navigation and active query observers report errors centrally. Imperative cached queries without an observer keep local error handling; delayed stale-client detection is an accepted tradeoff.
  • See the minimal route example.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Clean-code engineering standards for writing, refactoring, and reviewing code in any programming language. Use this whenever the user asks to write clean code, follow clean-code principles, refactor for clarity, improve naming, reduce complexity or duplication, separate concerns, tighten error handling, work test-first or do TDD, or otherwise raise code quality, readability, and maintainability. Also use when writing or reviewing Operately APIs or Ecto queries. Apply these rules by default when producing or changing code for a quality-conscious user.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

operately/operately5872026년 10월 17일 업데이트

Defines where UI components belong in Operately (TurboUI-first). Use when creating, changing, reviewing, or migrating UI components, adding features that need UI, or deciding whether to refactor legacy app UI in app/assets/js/components or app/assets/js/features. Covers pure TurboUI components, component reuse, the app bridge pattern, and legacy migration scenarios.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

operately/operately5872026년 10월 17일 업데이트

Rules for Operately schema migrations (app/priv/repo/migrations/) and data migrations (app/lib/operately/data/change_*.ex). Use when adding, renaming, reviewing, or generating database migrations, ecto.gen.migration, Operately.Data.Change* modules, backfills, schema_migrations version collisions, mix ecto.migrate, or make gen.migration.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

operately/operately5872026년 10월 17일 업데이트

help-docs

무료

Discover help documentation work from operately git history. Use when the user asks to audit what needs documenting since a release, tag, or SHA, or to identify documentation gaps from code changes. Requires a baseline SHA or tag as input.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

operately/operately5872026년 10월 17일 업데이트

Maintain Operately translations when adding or changing user-visible copy, fixing missing translations, or adding a supported language. Covers shared Gettext/i18next catalogs, glossaries, generation, and completeness checks; excludes translating user-authored content or general prose outside the product.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

operately/operately5872026년 10월 17일 업데이트

mcp-tools

무료

Defines how to add Operately MCP tools (API-first wrappers). Use when creating, changing, or reviewing MCP tools under app/lib/operately_web/mcp/tools/, when the user mentions MCP tools, tool catalog, @expected_tool_names, or when exposing a new Operately capability to ChatGPT/Claude MCP clients.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

operately/operately5872026년 10월 17일 업데이트

operately의 스킬 모두 보기

이 스킬의 문제 신고