{"id":"ebb0d4ad-1d71-44b2-97d8-580a31795a53","shortId":"Svt5BZ","kind":"skill","title":"tanstack","tagline":"Use when editing TanStack code, @tanstack imports, useQuery, createRouter, React Query, TanStack Router, Table, Form, Store, file-based routing, data fetching, or SPA state management.","description":"# TanStack Ecosystem\n\nThe TanStack ecosystem provides standard libraries for modern React/TypeScript applications, emphasizing type safety, performance, and developer experience.\n\n## Quick Reference\n\n### useQuery Pattern (with Query Options Factory)\n\n```tsx\nimport { queryOptions, useQuery } from '@tanstack/react-query'\n\n// Define query options as a factory -- reusable across components and loaders\nexport const usersQueryOptions = (filters?: UserFilters) =>\n  queryOptions({\n    queryKey: ['users', filters],\n    queryFn: () => api.getUsers(filters),\n    staleTime: 5 * 60 * 1000, // 5 minutes\n  })\n\nfunction UsersPage() {\n  const { data, isLoading, error } = useQuery(usersQueryOptions())\n\n  if (isLoading) return <Spinner />\n  if (error) return <ErrorMessage error={error} />\n\n  return <UserList users={data} />\n}\n```\n\n### Mutations with Cache Invalidation\n\n```tsx\nexport function useCreateUser() {\n  const queryClient = useQueryClient()\n\n  return useMutation({\n    mutationFn: (data: UserCreate) => api.createUser(data),\n    onSuccess: () => {\n      queryClient.invalidateQueries({ queryKey: ['users'] })\n    },\n  })\n}\n```\n\n### File-Based Routing (TanStack Router)\n\n```text\nsrc/routes/\n├── __root.tsx           # Root layout\n├── index.tsx            # / route\n├── _layout.tsx          # Layout wrapper (no URL segment)\n├── users/\n│   ├── index.tsx        # /users\n│   ├── $userId.tsx      # /users/:userId\n│   └── $userId.edit.tsx # /users/:userId/edit\n```\n\n### Route with Loader & Query Pre-fetching\n\n```tsx\nimport { createFileRoute } from '@tanstack/react-router'\nimport { queryClient } from '@/lib/query-client'\n\nexport const Route = createFileRoute('/users')({\n  loader: () => queryClient.ensureQueryData(usersQueryOptions()),\n  component: UsersPage,\n})\n\n// Route parameters\nexport const Route = createFileRoute('/users/$userId')({\n  loader: ({ params }) =>\n    queryClient.ensureQueryData(userQueryOptions(params.userId)),\n  component: UserDetailPage,\n})\n```\n\n### Search Parameters (Zod Validation)\n\n```tsx\nimport { z } from 'zod'\n\nconst searchSchema = z.object({\n  page: z.number().default(1),\n  sort: z.enum(['name', 'date']).default('name'),\n})\n\nexport const Route = createFileRoute('/users')({\n  validateSearch: searchSchema,\n  component: UsersPage,\n})\n```\n\n### TanStack Table Basics\n\n```tsx\nimport { useReactTable, getCoreRowModel, flexRender, ColumnDef } from '@tanstack/react-table'\n\nconst columns: ColumnDef<User>[] = [\n  { accessorKey: 'name', header: 'Name' },\n  { accessorKey: 'email', header: 'Email' },\n  {\n    accessorKey: 'createdAt',\n    header: 'Joined',\n    cell: (info) => new Date(info.getValue<string>()).toLocaleDateString(),\n  },\n]\n\nfunction UsersTable({ users }: { users: User[] }) {\n  const table = useReactTable({\n    data: users,\n    columns,\n    getCoreRowModel: getCoreRowModel(),\n  })\n  // render with flexRender -- see references/table.md\n}\n```\n\n<workflow>\n\n## Workflow\n\n### Step 1: Identify the Library\n\n| Need | Library | Key Import |\n| --- | --- | --- |\n| Data fetching & caching | TanStack Query | `@tanstack/react-query` |\n| Client-side routing | TanStack Router | `@tanstack/react-router` |\n| Table / data grid | TanStack Table | `@tanstack/react-table` |\n| Form state & validation | TanStack Form | `@tanstack/react-form` |\n| Lightweight state | TanStack Store | `@tanstack/store` |\n\n### Step 2: Implement\n\n1. **Query**: Define query options factories with `queryOptions()` -- always set `staleTime`\n2. **Router**: Use file-based routing with `createFileRoute` -- pre-fetch with `loader`\n3. **Table**: Define `ColumnDef[]` typed to your data -- use `getCoreRowModel()` as base\n4. **Form**: Use `useForm()` with Zod adapter for validation\n\n### Step 3: Integrate Router + Query\n\n1. Create query options factories in a shared location (e.g., `@/lib/queries/`)\n2. Use `ensureQueryData` in route loaders for data pre-fetching\n3. Use the same query options in components with `useQuery` for cache hits\n4. Prefetch on hover with `queryClient.prefetchQuery()` for navigation links\n\n### Step 4: Validate\n\nRun through the validation checkpoint below before considering the work complete.\n\n</workflow>\n\n<guardrails>\n\n## Guardrails\n\n- **Always set `staleTime`** on queries -- the default (0) causes unnecessary refetches on every mount\n- **Always use `queryKey` arrays** -- include all variables the query depends on: `['users', filters]`\n- **Always use `queryOptions()` factory** -- makes query keys reusable across components and loaders\n- **Always handle loading and error states** -- `isLoading`, `error` from `useQuery` must be checked\n- **Prefetch on hover** for navigation links -- use `queryClient.prefetchQuery()` in `onMouseEnter`\n- **Never use inline queryFn without queryKey** -- keys must be stable and serializable\n- **Never mutate query data directly** -- use `queryClient.setQueryData()` for optimistic updates\n- **TanStack Router is NOT react-router** -- do not mix `<Link>` components or hooks between them\n\n</guardrails>\n\n<validation>\n\n### Validation Checkpoint\n\nBefore delivering TanStack code, verify:\n\n- [ ] All `useQuery` calls have `staleTime` set (via `queryOptions` factory or directly)\n- [ ] Query keys include all dependent variables (no stale closures)\n- [ ] Loading and error states are handled in every component that fetches data\n- [ ] Route loaders use `ensureQueryData` (not `fetchQuery`) to leverage cache\n- [ ] Mutations invalidate related query keys on success\n- [ ] Table column definitions are typed with `ColumnDef<T>[]`\n\n</validation>\n\n<example>\n\n## Example\n\n**Task:** \"Create a users list page with TanStack Router + Query, including search, pagination, and prefetch on hover.\"\n\n```tsx\n// --- lib/queries/users.ts ---\nimport { queryOptions } from '@tanstack/react-query'\nimport { api } from '@/lib/api'\n\ninterface UserFilters {\n  search?: string\n  page?: number\n}\n\nexport const usersQueryOptions = (filters: UserFilters = {}) =>\n  queryOptions({\n    queryKey: ['users', filters],\n    queryFn: () => api.getUsers(filters),\n    staleTime: 5 * 60 * 1000,\n  })\n\nexport const userQueryOptions = (userId: string) =>\n  queryOptions({\n    queryKey: ['users', userId],\n    queryFn: () => api.getUser(userId),\n    staleTime: 5 * 60 * 1000,\n  })\n\n\n// --- routes/users/index.tsx ---\nimport { createFileRoute } from '@tanstack/react-router'\nimport { z } from 'zod'\nimport { usersQueryOptions } from '@/lib/queries/users'\nimport { queryClient } from '@/lib/query-client'\n\nconst searchSchema = z.object({\n  search: z.string().optional(),\n  page: z.number().default(1),\n})\n\nexport const Route = createFileRoute('/users/')({\n  validateSearch: searchSchema,\n  loader: ({ search }) =>\n    queryClient.ensureQueryData(usersQueryOptions(search)),\n  component: UsersPage,\n})\n\nfunction UsersPage() {\n  const { search, page } = Route.useSearch()\n  const navigate = Route.useNavigate()\n  const { data, isLoading, error } = useQuery(\n    usersQueryOptions({ search, page }),\n  )\n\n  if (isLoading) return <Spinner />\n  if (error) return <ErrorMessage error={error} />\n\n  return (\n    <div>\n      <SearchInput\n        value={search ?? ''}\n        onChange={(value) => navigate({ search: { search: value, page: 1 } })}\n      />\n      <UserList users={data.items} />\n      <Pagination\n        page={page}\n        totalPages={data.totalPages}\n        onPageChange={(p) => navigate({ search: { search, page: p } })}\n      />\n    </div>\n  )\n}\n\n\n// --- components/UserLink.tsx ---\nimport { Link } from '@tanstack/react-router'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { userQueryOptions } from '@/lib/queries/users'\n\nfunction UserLink({ userId, name }: { userId: string; name: string }) {\n  const queryClient = useQueryClient()\n\n  return (\n    <Link\n      to=\"/users/$userId\"\n      params={{ userId }}\n      onMouseEnter={() => {\n        queryClient.prefetchQuery(userQueryOptions(userId))\n      }}\n    >\n      {name}\n    </Link>\n  )\n}\n```\n\n</example>\n\n## References Index\n\nFor detailed guides and code examples, refer to the following documents in `references/`:\n\n- **[Router](references/router.md)** -- File-based routing, parameters, navigation, and loaders.\n- **[Query](references/query.md)** -- Cache management, query factories, mutations, and optimistic updates.\n- **[Table](references/table.md)** -- Headless table logic and integration.\n- **[Form](references/form.md)** -- State management and validation adapters (Zod).\n- **[Store](references/store.md)** -- Lightweight client-side state management.\n\n## Official References\n\n- <https://tanstack.com/router/>\n- <https://tanstack.com/query/>\n- <https://tanstack.com/table/>\n- <https://tanstack.com/form/>\n- <https://tanstack.com/store/>\n\n## Shared Styleguide Baseline\n\n- Use shared styleguides for generic language/framework rules to reduce duplication in this skill.\n- [General Principles](https://github.com/cofin/flow/blob/main/templates/styleguides/general.md)\n- [TanStack](https://github.com/cofin/flow/blob/main/templates/styleguides/frameworks/tanstack.md)\n- [TypeScript](https://github.com/cofin/flow/blob/main/templates/styleguides/languages/typescript.md)\n- Keep this skill focused on tool-specific workflows, edge cases, and integration details.","tags":["tanstack","flow","cofin","agent-skills","ai-agents","beads","claude-code","codex","cursor","developer-tools","gemini-cli","opencode"],"capabilities":["skill","source-cofin","skill-tanstack","topic-agent-skills","topic-ai-agents","topic-beads","topic-claude-code","topic-codex","topic-cursor","topic-developer-tools","topic-gemini-cli","topic-opencode","topic-plugin","topic-slash-commands","topic-spec-driven-development"],"categories":["flow"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/cofin/flow/tanstack","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add cofin/flow","source_repo":"https://github.com/cofin/flow","install_from":"skills.sh"}},"qualityScore":"0.455","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 11 github stars · SKILL.md body (9,180 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:07:39.969Z","embedding":null,"createdAt":"2026-04-23T13:04:02.207Z","updatedAt":"2026-05-18T19:07:39.969Z","lastSeenAt":"2026-05-18T19:07:39.969Z","tsv":"'/cofin/flow/blob/main/templates/styleguides/frameworks/tanstack.md)':892 '/cofin/flow/blob/main/templates/styleguides/general.md)':888 '/cofin/flow/blob/main/templates/styleguides/languages/typescript.md)':896 '/form/':864 '/lib/api':624 '/lib/queries':387 '/lib/queries/users':675,769 '/lib/query-client':176,679 '/query/':858 '/router/':855 '/store/':867 '/table/':861 '/users':154,156,159,181,193,228,694,784 '0':443 '1':217,285,326,377,689,741 '1000':87,646,662 '2':324,337,388 '3':351,373,399 '4':363,412,422 '5':85,88,644,660 '60':86,645,661 'accessorkey':247,251,255 'across':68,471 'adapt':369,841 'alway':334,436,450,463,475 'api':622 'api.createuser':127 'api.getuser':657 'api.getusers':82,641 'applic':39 'array':453 'base':20,135,342,362,812 'baselin':870 'basic':235 'cach':113,295,410,582,820 'call':544 'case':907 'caus':444 'cell':259 'check':487 'checkpoint':428,536 'client':300,847 'client-sid':299,846 'closur':561 'code':6,540,799 'column':245,275,591 'columndef':241,246,354,596 'complet':434 'compon':69,185,200,231,406,472,530,570,702 'components/userlink.tsx':757 'consid':431 'const':73,92,119,178,190,211,225,244,270,632,648,680,691,706,710,713,778 'creat':378,599 'createdat':256 'createfilerout':170,180,192,227,345,665,693 'createrout':10 'data':22,93,110,125,128,273,293,307,358,395,513,573,714 'data.items':744 'data.totalpages':749 'date':221,262 'default':216,222,442,688 'defin':61,328,353 'definit':592 'deliv':538 'depend':459,557 'detail':796,910 'develop':45 'direct':514,552 'document':805 'duplic':880 'e.g':386 'ecosystem':29,32 'edg':906 'edit':4 'email':252,254 'emphas':40 'ensurequerydata':390,577 'error':95,102,105,106,479,482,564,716,725,728,729 'errormessag':104,727 'everi':448,569 'exampl':597,800 'experi':46 'export':72,116,177,189,224,631,647,690 'factori':54,66,331,381,466,550,823 'fetch':23,167,294,348,398,572 'fetchqueri':579 'file':19,134,341,811 'file-bas':18,133,340,810 'filter':75,80,83,462,634,639,642 'flexrend':240,280 'focus':900 'follow':804 'form':16,312,316,364,835 'function':90,117,265,704,770 'general':884 'generic':875 'getcorerowmodel':239,276,277,360 'github.com':887,891,895 'github.com/cofin/flow/blob/main/templates/styleguides/frameworks/tanstack.md)':890 'github.com/cofin/flow/blob/main/templates/styleguides/general.md)':886 'github.com/cofin/flow/blob/main/templates/styleguides/languages/typescript.md)':894 'grid':308 'guardrail':435 'guid':797 'handl':476,567 'header':249,253,257 'headless':830 'hit':411 'hook':532 'hover':415,490,614 'identifi':286 'implement':325 'import':8,56,169,173,207,237,292,617,621,664,668,672,676,758,762,766 'includ':454,555,608 'index':794 'index.tsx':144,153 'info':260 'info.getvalue':263 'inlin':500 'integr':374,834,909 'interfac':625 'invalid':114,584 'isload':94,99,481,715,722 'join':258 'keep':897 'key':291,469,504,554,587 'language/framework':876 'layout':143,147 'layout.tsx':146 'leverag':581 'lib/queries/users.ts':616 'librari':35,288,290 'lightweight':318,845 'link':420,493,759,782 'list':602 'load':477,562 'loader':71,163,182,195,350,393,474,575,697,817 'locat':385 'logic':832 'make':467 'manag':27,821,838,850 'minut':89 'mix':529 'modern':37 'mount':449 'must':485,505 'mutat':111,511,583,824 'mutationfn':124 'name':220,223,248,250,773,776,792 'navig':419,492,711,736,752,815 'need':289 'never':498,510 'new':261 'number':630 'offici':851 'onchang':734 'onmouseent':497,788 'onpagechang':750 'onsuccess':129 'optimist':518,826 'option':53,63,330,380,404,685 'p':751,756 'page':214,603,629,686,708,720,740,746,747,755 'pagin':610,745 'param':196,786 'paramet':188,203,814 'params.userid':199 'pattern':50 'perform':43 'pre':166,347,397 'pre-fetch':165,346,396 'prefetch':413,488,612 'principl':885 'provid':33 'queri':12,52,62,164,297,327,329,376,379,403,440,458,468,512,553,586,607,818,822 'querycli':120,174,677,779 'queryclient.ensurequerydata':183,197,699 'queryclient.invalidatequeries':130 'queryclient.prefetchquery':417,495,789 'queryclient.setquerydata':516 'queryfn':81,501,640,656 'querykey':78,131,452,503,637,653 'queryopt':57,77,333,465,549,618,636,652 'quick':47 'react':11,525 'react-rout':524 'react/typescript':38 'reduc':879 'refer':48,793,801,807,852 'references/form.md':836 'references/query.md':819 'references/router.md':809 'references/store.md':844 'references/table.md':282,829 'refetch':446 'relat':585 'render':278 'return':100,103,107,122,723,726,730,781 'reusabl':67,470 'root':142 'root.tsx':141 'rout':21,136,145,161,179,187,191,226,302,343,392,574,692,813 'route.usenavigate':712 'route.usesearch':709 'router':14,138,304,338,375,521,526,606,808 'routes/users/index.tsx':663 'rule':877 'run':424 'safeti':42 'search':202,609,627,683,698,701,707,719,733,737,738,753,754 'searchinput':731 'searchschema':212,230,681,696 'see':281 'segment':151 'serializ':509 'set':335,437,547 'share':384,868,872 'side':301,848 'skill':883,899 'skill-tanstack' 'sort':218 'source-cofin' 'spa':25 'specif':904 'src/routes':140 'stabl':507 'stale':560 'staletim':84,336,438,546,643,659 'standard':34 'state':26,313,319,480,565,837,849 'step':284,323,372,421 'store':17,321,843 'string':628,651,775,777 'styleguid':869,873 'success':589 'tabl':15,234,271,306,310,352,590,828,831 'tanstack':1,5,7,13,28,31,137,233,296,303,309,315,320,520,539,605,889 'tanstack.com':854,857,860,863,866 'tanstack.com/form/':862 'tanstack.com/query/':856 'tanstack.com/router/':853 'tanstack.com/store/':865 'tanstack.com/table/':859 'tanstack/react-form':317 'tanstack/react-query':60,298,620,765 'tanstack/react-router':172,305,667,761 'tanstack/react-table':243,311 'tanstack/store':322 'task':598 'text':139 'tolocaledatestr':264 'tool':903 'tool-specif':902 'topic-agent-skills' 'topic-ai-agents' 'topic-beads' 'topic-claude-code' 'topic-codex' 'topic-cursor' 'topic-developer-tools' 'topic-gemini-cli' 'topic-opencode' 'topic-plugin' 'topic-slash-commands' 'topic-spec-driven-development' 'totalpag':748 'tsx':55,115,168,206,236,615 'type':41,355,594 'typescript':893 'unnecessari':445 'updat':519,827 'url':150 'use':2,339,359,365,389,400,451,464,494,499,515,576,871 'usecreateus':118 'useform':366 'usemut':123 'usequeri':9,49,58,96,408,484,543,717 'usequerycli':121,763,780 'user':79,109,132,152,267,268,269,274,461,601,638,654,743 'usercr':126 'userdetailpag':201 'usereactt':238,272 'userfilt':76,626,635 'userid':157,194,650,655,658,772,774,785,787,791 'userid.edit.tsx':158 'userid.tsx':155 'userid/edit':160 'userlink':771 'userlist':108,742 'userqueryopt':198,649,767,790 'userspag':91,186,232,703,705 'usersqueryopt':74,97,184,633,673,700,718 'userst':266 'valid':205,314,371,423,427,535,840 'validatesearch':229,695 'valu':732,735,739 'variabl':456,558 'verifi':541 'via':548 'without':502 'work':433 'workflow':283,905 'wrapper':148 'z':208,669 'z.enum':219 'z.number':215,687 'z.object':213,682 'z.string':684 'zod':204,210,368,671,842","prices":[{"id":"b2e48e6e-eed5-4ec5-8f11-1318ad5a4aa0","listingId":"ebb0d4ad-1d71-44b2-97d8-580a31795a53","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"cofin","category":"flow","install_from":"skills.sh"},"createdAt":"2026-04-23T13:04:02.207Z"}],"sources":[{"listingId":"ebb0d4ad-1d71-44b2-97d8-580a31795a53","source":"github","sourceId":"cofin/flow/tanstack","sourceUrl":"https://github.com/cofin/flow/tree/main/skills/tanstack","isPrimary":false,"firstSeenAt":"2026-04-23T13:04:02.207Z","lastSeenAt":"2026-05-18T19:07:39.969Z"}],"details":{"listingId":"ebb0d4ad-1d71-44b2-97d8-580a31795a53","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cofin","slug":"tanstack","github":{"repo":"cofin/flow","stars":11,"topics":["agent-skills","ai-agents","beads","claude-code","codex","context-driven-development","cursor","developer-tools","gemini-cli","opencode","plugin","slash-commands","spec-driven-development","subagents","tdd","workflow"],"license":"apache-2.0","html_url":"https://github.com/cofin/flow","pushed_at":"2026-04-27T19:07:26Z","description":"Context-Driven Development toolkit for AI agents — spec-first planning, TDD workflow, and Beads integration.","skill_md_sha":"33ff9f0bbb47a09c375679cbb9951e5d35723a7f","skill_md_path":"skills/tanstack/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cofin/flow/tree/main/skills/tanstack"},"layout":"multi","source":"github","category":"flow","frontmatter":{"name":"tanstack","description":"Use when editing TanStack code, @tanstack imports, useQuery, createRouter, React Query, TanStack Router, Table, Form, Store, file-based routing, data fetching, or SPA state management."},"skills_sh_url":"https://skills.sh/cofin/flow/tanstack"},"updatedAt":"2026-05-18T19:07:39.969Z"}}