| name | cache-management |
| description | Type-safe QueryClient with get/set/update/invalidate methods, predicate-based updates, cascade invalidation strategy, shared cache across components, lazy refetch patterns.
|
| type | core |
| library | vue-core-api-utils |
@wisemen/vue-core-api-utils — Cache Management
Manually read, write, update, and invalidate the query cache using the type-safe QueryClient wrapper. This is useful for optimistic updates and strategically invalidating affected queries.
Setup
import { useQueryClient } from '@/api'
const queryClient = useQueryClient()
const contact = queryClient.get(['contactDetail', { contactUuid: '123' }])
queryClient.set(
['contactDetail', { contactUuid: '123' }],
updatedContact
)
const { rollback } = queryClient.update('contactList', {
by: (contact) => contact.id === '123',
value: (contact) => ({ ...contact, name: 'Updated' }),
})
await queryClient.invalidate('contactList')
useQueryClient() is a helper you create in your @/api module (see getting-started) that wraps new QueryClient(getTanstackQueryClient()).
Core Patterns
Get cached data
const queryClient = useQueryClient()
const contact = queryClient.get(
['contactDetail', { contactUuid: '123' }]
)
const allContacts = queryClient.get('contactList')
const specificQuery = queryClient.get('contactList', { isExact: true })
Returns the cached data or null if not cached. The QueryClient infers entity type from your query key definition.
Set cached data
const queryClient = useQueryClient()
queryClient.set(
['contactDetail', { contactUuid: '123' }],
{ id: '123', name: 'John', email: 'john@email.com' }
)
queryClient.set('contactList', [
{ id: '123', name: 'John' },
{ id: '456', name: 'Jane' },
])
set() replaces all cached data for that query key.
Update cached data with predicates
const queryClient = useQueryClient()
const { rollback } = queryClient.update('contactList', {
by: (contact) => contact.id === '123',
value: (contact) => ({
...contact,
name: 'Updated John'
}),
})
const { rollback: rollbackDetail } = queryClient.update('contactDetail', {
by: (contact) => true,
value: (contact) => ({ ...contact, name: 'Updated' }),
})
update() returns { rollback } — a function that reverts the cache to its previous state. Use this for optimistic updates. QueryClient knows whether the entity is an array or single item, so predicates work transparently on lists.
Invalidate and refetch
const queryClient = useQueryClient()
await queryClient.invalidate('contactList')
await queryClient.invalidate(['contactDetail', { contactUuid: '123' }])
Invalidation marks cached data as stale. The next interaction (component mount, user action) triggers a refetch.
Cache Strategy
Explicitly invalidate only the queries affected by the mutation. Let lazy refetch handle the rest when users navigate to pages needing other data.
— Maintainer guidance
When a mutation succeeds, look at what changed:
- If you updated a contact, invalidate
contactDetail and contactList (they both show that contact)
- If you archived a conversation, invalidate
conversationList (but maybe not conversationDetail unless showing the one you archived)
- Don't invalidate unrelated queries — let them refetch lazily when needed
Shared Cache Across Components
Important: Multiple components using the same query key share the same cached data. This is a feature, not a bug.
const { result: resultA } = useQuery('userDetail', {
params: { id: computed(() => 'same-id') },
queryFn: () => UserService.getById('same-id'),
})
const { result: resultB } = useQuery('userDetail', {
params: { id: computed(() => 'same-id') },
queryFn: () => UserService.getById('same-id'),
})
Use this to your advantage: invalidate a query and all components using it refetch automatically.
See Also