Skip to content

Documentation - Tags & Invalidation#61

Merged
stackoverfloweth merged 9 commits intomainfrom
docs-tags
Oct 25, 2025
Merged

Documentation - Tags & Invalidation#61
stackoverfloweth merged 9 commits intomainfrom
docs-tags

Conversation

@stackoverfloweth
Copy link
Copy Markdown
Contributor

Continue working through missing documentation pages

side quest:

  • include everything in main export off the default client
  • update queries docs to reflect recent changes to params syntax

@stackoverfloweth stackoverfloweth self-assigned this Oct 17, 2025
# Migrating from Tanstack

- no keys, caches function + arguments by default
- no keys (tags don't require user to adopt naming scheme), caches function + arguments by default
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know both these libraries and this is confusing to me. I'd keep it as no keys. Because in tanstack keys are required to even make a query. You don't need to use tags at all. In fact some simple apps I'd expect to not need tags.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file is not done, just taking notes along the way

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe don't commit unfinished pages or mark things that don't need reviewed. Just so I can skip them

}

const catsQuery = query(searchCats, ['Maine Coon'])
const catsQuery = query(searchCats, () => ['Maine Coon'])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we update the query function to accept a getter? I thought last we left it query accepted an array and use query accepted a getter.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh duh, yeah I misremembered our change was only for useQuery. Good catch

# Tags & Invalidation

<!-- Tags provide a powerful way to organize and invalidate cached queries in Kitbag query. They allow you to group related queries and efficiently update your cache when data changes.
When it comes time to refresh your query, there are a few options. First, each query has an `execute()` function, which will force the query to refetch and update the reactive `data` for your query but also any other query that shares the same function and arguments.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This second sentence is pretty long. Maybe break it up a bit.

Also I'd avoid "fetch" terminology. Actions are just functions. They don't necessarily fetch

## Tags

const { queryClient } = createQueryClient()
Often we have multiple queries to different functions that are related to each other. For example, any queries that use the current user token in API headers would probably want to be called again if the token changes. Ideally these queries that are otherwise unrelated could be grouped together.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be simpler to understand if it was user id rather than token.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose token and API headers specifically because I thought it was a good example of a thing that could change and is NOT gonna be an argument for the query functions. I feel like userId could be an argument for fetchMe or maybe more so getNotifications in the readers mind

invalidateTags: [userTag, profileTag]
}
)
export const requiresUserContext = tag()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this example feels to complex to me. Let's make a cute cat example. We just need to show creating a tag, adding the tag to two queries, and then refreshing by the tag.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried something simpler and cat themed. Let's see what you think of this version

## Tag Type

Invalidate queries based on conditions:
Kitbag Query also tracks a generic on each tag, which By default is `unknown`. This satisfies any query, but assigning your actual type not only validates the query type but also provides improved type safety later when accessing cached data, like in `setQueryData`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Kitbag Query also supports typed tags. Where a generic type can be added to a tag for extra type safety"

Or something along those lines. We don't tract generics. We support typed tags. (That's like a concept, typed tags)

const dashboardTag = tag('dashboard')
const analyticsTag = tag('analytics')
const settingsTag = tag('settings')
As an example, let's group some queries that all use the same external API. That way when the API version changes, we can update all the queries at once because they share the same tag.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're going to have to walk me through the thought behind this sort of example. Tags are really intended to be a grouping of related data. So I don't really understand this use case.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd just make the tags catsTag and refresh all cats data. The api version stuff is distracting IMO.

## Advanced Invalidation Patterns
const catNamesQuery = useQuery(randomCatNames, () => [...], { tags: [usesCatsApi] })
const catYearsQuery = useQuery(convertToCatYears, () => [...], { tags: [usesCatsApi] })
const catYearsQuery = useQuery(checkCatIsNapping, () => [...], { tags: [usesCatsApi] })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

catNappingQuery

### Background Invalidation

Invalidate without showing loading states:
Kitbag Query also supports typed tags. Where a generic type can be added to a tag for extra type safety. By default the tag type is `unknown`. This satisfies any query, but assigning your actual type not only validates the query type but also provides improved type safety later when accessing cached data, like in `setQueryData`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how its suppose to work, but be aware its more of a concept then actually working code at this point.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works for basic cases, but once you start having multiple types it kinda falls apart.

Comment on lines +62 to +76
::: code-group

const userQuery = query('user', fetchUser, {
tags: [userTag]
})
```vue [components/CatViewer.vue]
<script lang="ts" setup>
import { useQuery } from '@kitbag/query'

const userFriendsQuery = query('user-friends', fetchUserFriends, {
tags: [userTag, userRelationsTag]
})
const { catId } = defineProps<{
catId: string
}>()

const userGroupsQuery = query('user-groups', fetchUserGroups, {
tags: [userTag, userRelationsTag]
})

// Update user profile - only invalidate direct user data
userMutation.mutate(userData, {
invalidateTags: [userTag]
})

// Update friend relationship - invalidate user relations
addFriendMutation.mutate(friendData, {
invalidateTags: [userRelationsTag]
})
const query = useQuery(fetchCat, () => [catId])
</script>
```

### By Access Patterns

Group by how data is accessed:

```ts
const listTag = tag('list') // List views
const detailTag = tag('detail') // Detail views
const searchTag = tag('search') // Search results

const usersListQuery = query('users-list', fetchUsers, {
tags: [userTag, listTag]
})

const userDetailQuery = query('user-detail', fetchUserDetail, {
tags: [userTag, detailTag]
})

const userSearchQuery = query('user-search', searchUsers, {
tags: [userTag, searchTag]
})
```
:::
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd skip this example, we already went over how to create queries. Just show a typed tag example.

```

### 2. Don't Over-Tag
```vue [components/CatViewer.vue]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two parts example isn't necessary IMO. We can just create a typed tag, create a tagged query with that tag, then call refreshData with the tag with a specific value. And show that it'll refresh the query only if the id matches.

# Migrating from Tanstack

- no keys, caches function + arguments by default
- no keys (tags don't require user to adopt naming scheme), caches function + arguments by default
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe don't commit unfinished pages or mark things that don't need reviewed. Just so I can skip them

Copy link
Copy Markdown
Contributor

@craig-pplx craig-pplx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple last suggestions. Otherwise very clean and concise. Love it

}
)
// invalidates only queries that call searchCats with ['Himalayan']
refreshQueryData(searchCats, () => ['Himalayan'])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this uses a getter. It's not reactive.

search: tag('search')
function save(catId: string): Promise<void> {
...
refreshQueryData(catIdTag(catId))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along with this we should show how on a query the tags can be a getter. So the list of cats and be tagged based on each cat in the list. That way if cat 1 is refreshed, the list of cats (which contains cat 1) is refreshed as well.

stackoverfloweth and others added 2 commits October 24, 2025 13:47
Co-authored-by: Craig Harshbarger <craig.harshbarger@perplexity.ai>
@stackoverfloweth stackoverfloweth merged commit 488a52a into main Oct 25, 2025
2 checks passed
@stackoverfloweth stackoverfloweth deleted the docs-tags branch October 25, 2025 01:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants