Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,180 changes: 2,180 additions & 0 deletions _artifacts/domain_map.yaml

Large diffs are not rendered by default.

189 changes: 189 additions & 0 deletions _artifacts/skill_spec.md

Large diffs are not rendered by default.

704 changes: 704 additions & 0 deletions _artifacts/skill_tree.yaml

Large diffs are not rendered by default.

62 changes: 62 additions & 0 deletions _artifacts/tkdodo_cross_check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# TKDodo Cross-Check

Checked on 2026-06-03 against `https://tkdodo.eu/blog/tan-stack-router-and-query`, which lists 33 React Query series parts, and `https://tkdodo.eu/blog/all` for adjacent Query-tagged posts.

## Coverage Decisions

| Priority | Post | Coverage |
| -------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| #1 | Practical React Query | `fetch-and-observe-queries`, `tune-defaults-freshness-retries-and-refetching` |
| #2 | React Query Data Transformations | `seed-placeholder-select-and-transform-data`, `selectors-and-derived-state` |
| #3 | React Query Render Optimizations | `shape-data-and-render-efficiently`, `selectors-and-derived-state` |
| #4 | Status Checks in React Query | Added `handle-status-and-errors` |
| #5 | Testing React Query | `test-query-code` |
| #6 | React Query and TypeScript | `design-query-keys-and-options`, `build-query-abstractions` |
| #7 | Using WebSockets with React Query | `broadcast-realtime-and-multi-tab-synchronization` |
| #8 | Effective React Query Keys | `design-query-keys-and-options` |
| #8a | Leveraging the Query Function Context | `design-query-keys-and-options`, `cancel-queries-and-consume-abort-signals` |
| #9 | Placeholder and Initial Data in React Query | `seed-placeholder-select-and-transform-data` |
| #10 | React Query as a State Manager | `selectors-and-derived-state`, `query-data-and-forms` |
| #11 | React Query Error Handling | Added `handle-status-and-errors` |
| #12 | Mastering Mutations in React Query | `write-mutations-and-invalidate-related-queries` |
| #13 | Offline React Query | `persist-offline-and-restore-caches` |
| #14 | React Query and Forms | Added `query-data-and-forms` |
| #15 | React Query FAQs | Distributed across keys, mutations, defaults, forms, and status skills |
| #16 | React Query meets React Router | `prefetch-and-remove-request-waterfalls`; React Router remains secondary to TanStack Router/Start |
| #17 | Seeding the Query Cache | `seed-placeholder-select-and-transform-data` |
| #18 | Inside React Query | Added `understand-query-internals-and-observers` |
| #19 | Type-safe React Query | `design-query-keys-and-options`, `build-query-abstractions` |
| #20 | You Might Not Need React Query | `ssr-hydration-and-streaming`; no separate skill yet because it is decision guidance |
| #21 | Thinking in React Query | Distributed mental-model coverage, especially `understand-query-internals-and-observers` |
| #22 | React Query and React Context | `setup-query-client-and-providers`, `use-framework-adapter-reactivity` |
| #23 | Why You Want React Query | Introductory rationale, covered by higher-signal operational skills |
| #24 | The Query Options API | `design-query-keys-and-options`, added `build-query-abstractions` |
| #25 | Automatic Query Invalidation after Mutations | Added `automatic-invalidation-after-mutations` |
| #26 | How Infinite Queries work | `paginate-and-build-infinite-queries` |
| #27 | React Query API Design - Lessons Learned | `build-query-abstractions`, `migrate-major-versions-and-codemods` |
| #28 | React Query - The Bad Parts | Cross-cutting tradeoffs in `query-data-and-forms`, `handle-status-and-errors`, `ssr-hydration-and-streaming` |
| #29 | Concurrent Optimistic Updates in React Query | Added `concurrent-optimistic-updates` |
| #30 | React Query Selectors, Supercharged | Added `selectors-and-derived-state` |
| #31 | Creating Query Abstractions | Added `build-query-abstractions` |
| #32 | TanStack Router and Query | Tightened `compose-query-with-tanstack-router-and-start` |

## Adjacent Posts Promoted

| Post | Reason | Coverage |
| ----------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------ |
| Deriving Client State from Server State | Directly informs forms, selectors, and state-sync avoidance | `selectors-and-derived-state`, `query-data-and-forms` |
| React 19 and Suspense - A Drama in 3 Acts | Relevant to suspense and streaming but React-version specific | `use-suspense-and-error-boundaries`, `ssr-hydration-and-streaming` |

## Skills Added From This Audit

- `framework/handle-status-and-errors`
- `compositions/query-data-and-forms`
- `compositions/automatic-invalidation-after-mutations`
- `core/concurrent-optimistic-updates`
- `core/selectors-and-derived-state`
- `core/build-query-abstractions`
- `core/understand-query-internals-and-observers`

## Existing Skill Tightened

- `compositions/compose-query-with-tanstack-router-and-start`: added TKDodo #32 source and guidance to use one QueryClient in router/provider, set `defaultPreloadStaleTime: 0`, treat loaders as cache priming, and read route data through Query observers instead of `useLoaderData`.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"@eslint-react/eslint-plugin": "^2.0.1",
"@size-limit/preset-small-lib": "^12.0.0",
"@tanstack/eslint-config": "0.3.2",
"@tanstack/intent": "^0.0.41",
"@tanstack/query-intent": "workspace:*",
"@tanstack/typedoc-config": "0.3.1",
"@tanstack/vite-config": "0.4.3",
"@testing-library/jest-dom": "^6.8.0",
Expand Down
29 changes: 29 additions & 0 deletions packages/query-intent/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@tanstack/query-intent",
"version": "5.101.0",
"description": "Intent skills for TanStack Query, framework adapters, and ecosystem integrations",
"author": "tannerlinsley",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/TanStack/query.git",
"directory": "packages/query-intent"
},
"homepage": "https://tanstack.com/query",
"type": "module",
"keywords": [
"tanstack-intent"
],
"files": [
"skills",
"!skills/_artifacts"
],
"intent": {
"version": 1,
"repo": "TanStack/query",
"docs": "https://tanstack.com/query"
},
"devDependencies": {
"@tanstack/intent": "^0.0.41"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
name: compositions/automatic-invalidation-after-mutations
description: >
Use this when designing automatic invalidation policies for TanStack Query
mutations with MutationCache callbacks, mutationKey-to-queryKey matching,
mutation meta invalidation tags, awaited invalidation, and exclusions for
static or unrelated queries.
type: composition
library: TanStack Query
library_version: '5.101.0'
requires:
- core/write-mutations-and-invalidate-related-queries
- core/design-query-keys-and-options
sources:
- https://tkdodo.eu/blog/automatic-query-invalidation-after-mutations
- TanStack/query:docs/framework/react/guides/invalidations-from-mutations.md
- TanStack/query:docs/reference/MutationCache.md
- TanStack/query:docs/reference/QueryClient.md
---

## Core Patterns

Use local mutation callbacks for one-off behavior. Use `MutationCache` callbacks when the app wants a consistent invalidation policy for every mutation.

### Global invalidation after successful mutations

```ts
import { MutationCache, QueryClient } from '@tanstack/react-query'

export const queryClient = new QueryClient({
mutationCache: new MutationCache({
onSuccess: (_data, _variables, _context, mutation) => {
return queryClient.invalidateQueries({
queryKey: mutation.options.mutationKey,
})
},
}),
})
```

If a mutation has `mutationKey: ['issues']`, this invalidates matching issue queries. If it has no mutation key, this becomes a broad invalidation policy, so only use that deliberately.

### Use meta for explicit invalidation tags

```ts
import { matchQuery, MutationCache, QueryClient } from '@tanstack/react-query'

const queryClient = new QueryClient({
mutationCache: new MutationCache({
onSuccess: (_data, _variables, _context, mutation) => {
return queryClient.invalidateQueries({
predicate: (query) =>
mutation.meta?.invalidates?.some((queryKey) =>
matchQuery({ queryKey }, query),
) ?? true,
})
},
}),
})
```

## Common Mistakes

### HIGH Invalidating the whole app for every mutation

Wrong:

```ts
new MutationCache({
onSuccess: () => queryClient.invalidateQueries(),
})
```

Correct:

```ts
new MutationCache({
onSuccess: (_data, _variables, _context, mutation) =>
queryClient.invalidateQueries({ queryKey: mutation.options.mutationKey }),
})
```

Global policies need scope. Reach for mutation keys or meta tags before invalidating everything.

Source: https://tkdodo.eu/blog/automatic-query-invalidation-after-mutations

### HIGH Not returning invalidation when pending UI depends on refetch

Wrong:

```ts
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
}
```

Correct:

```ts
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] })
```

Returning the promise keeps the mutation pending until the invalidation refetch completes.

Source: TanStack/query:docs/framework/react/guides/invalidations-from-mutations.md

### MEDIUM Refetching data that should be static

Wrong:

```ts
useQuery({
queryKey: ['build-info'],
queryFn: fetchBuildInfo,
staleTime: Infinity,
})
```

Correct:

```ts
useQuery({
queryKey: ['build-info'],
queryFn: fetchBuildInfo,
staleTime: 'static',
})
```

If a query must not refetch even after broad manual invalidation, mark it with `staleTime: 'static'`.

Source: https://tkdodo.eu/blog/automatic-query-invalidation-after-mutations
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
name: compositions/broadcast-realtime-and-multi-tab-synchronization
description: >
Use this when synchronizing TanStack Query caches with broadcastQueryClient,
BroadcastChannel, multi-tab cache sync, realtime invalidation, WebSocket
events, server/browser boundaries, and experimental broadcast behavior.
type: composition
library: TanStack Query
library_version: '5.101.0'
requires:
- lifecycle/setup-query-client-and-providers
- core/write-mutations-and-invalidate-related-queries
- compositions/persist-offline-and-restore-caches
sources:
- TanStack/query:docs/framework/react/plugins/broadcastQueryClient.md
- TanStack/query:docs/framework/react/guides/query-invalidation.md
- TanStack/query:examples/react/chat/package.json
---

## Setup

```ts
import { QueryClient } from '@tanstack/react-query'
import { broadcastQueryClient } from '@tanstack/query-broadcast-client-experimental'

export const queryClient = new QueryClient()

if (typeof window !== 'undefined') {
broadcastQueryClient({ queryClient, broadcastChannel: 'app-query-cache' })
}
```

## Core Integration Patterns

### Invalidate from realtime events

```ts
import { QueryClient } from '@tanstack/react-query'

const queryClient = new QueryClient()

export function onTodoChanged(todoId: number) {
return queryClient.invalidateQueries({ queryKey: ['todo', todoId] })
}
```

### Update narrow cache slices

```ts
import { QueryClient } from '@tanstack/react-query'

const queryClient = new QueryClient()

export function onTodoTitle(todo: { id: number; title: string }) {
queryClient.setQueryData(['todo', todo.id], todo)
}
```

### Keep broadcast client-side

```ts
import { QueryClient } from '@tanstack/react-query'
import { broadcastQueryClient } from '@tanstack/query-broadcast-client-experimental'

export function installBroadcast(queryClient: QueryClient) {
if (typeof window === 'undefined') return
broadcastQueryClient({ queryClient, broadcastChannel: 'query-cache' })
}
```

## Common Mistakes

### MEDIUM Unlocked experimental broadcast

Wrong:

```ts
import { QueryClient } from '@tanstack/react-query'
import { broadcastQueryClient } from '@tanstack/query-broadcast-client-experimental'

broadcastQueryClient({ queryClient: new QueryClient() })
```

Correct:

```ts
import { QueryClient } from '@tanstack/react-query'
import { broadcastQueryClient } from '@tanstack/query-broadcast-client-experimental'

if (typeof window !== 'undefined') {
broadcastQueryClient({
queryClient: new QueryClient(),
broadcastChannel: 'query-cache-v1',
})
}
```

The broadcast package is experimental and should be deliberately isolated behind browser-only setup.

Source: TanStack/query:docs/framework/react/plugins/broadcastQueryClient.md

### MEDIUM Over-normalized realtime writes

Wrong:

```ts
import { QueryClient } from '@tanstack/react-query'

const queryClient = new QueryClient()
queryClient.setQueryData(['todos'], { byId: { 1: { id: 1 } }, allIds: [1] })
```

Correct:

```ts
import { QueryClient } from '@tanstack/react-query'

const queryClient = new QueryClient()
queryClient.invalidateQueries({ queryKey: ['todos'] })
```

Query is not a normalized cache; invalidation is often safer than duplicating server state models.

Source: TanStack/query:docs/framework/react/guides/query-invalidation.md

### HIGH Broadcast on server

Wrong:

```ts
import { QueryClient } from '@tanstack/react-query'
import { broadcastQueryClient } from '@tanstack/query-broadcast-client-experimental'

export const queryClient = new QueryClient()
broadcastQueryClient({ queryClient })
```

Correct:

```ts
import { QueryClient } from '@tanstack/react-query'
import { broadcastQueryClient } from '@tanstack/query-broadcast-client-experimental'

export const queryClient = new QueryClient()
if (typeof window !== 'undefined') broadcastQueryClient({ queryClient })
```

BroadcastChannel is a browser primitive; server setup should not install it.

Source: TanStack/query:docs/framework/react/plugins/broadcastQueryClient.md
Loading
Loading