Skip to main content

invalidateQueriesForKeys

A batch helper function that invalidates multiple TanStack Query cache entries based on an array of QueryKeys.

Type Signature​

export const invalidateQueriesForKeys = (
keys: Array<QueryKey<string>>,
invalidateOptions?: InvalidateQueryFilters,
): void

Parameters​

keys​

  • Type: Array<QueryKey<string>>
  • Required: Yes
  • Description: Array of QueryKey objects to invalidate

invalidateOptions​

  • Type: InvalidateQueryFilters (from @tanstack/react-query)
  • Required: No
  • Description: Additional options passed to queryClient.invalidateQueries()

Return Value​

  • Type: void
  • Description: This function performs side effects (invalidation) and returns nothing

How It Works​

The function iterates through the provided array of QueryKeys and calls queryClient.invalidateQueries() for each one. It automatically filters out any null or undefined values.

export const invalidateQueriesForKeys = (
keys: Array<QueryKey<string>>,
invalidateOptions?: InvalidateQueryFilters,
): void => {
keys.filter(Boolean).forEach((key) => {
queryClient.invalidateQueries({ queryKey: [key], ...invalidateOptions });
});
};

Basic Example​

import { invalidateQueriesForKeys } from 'src/queries';
import { accountsQueryGroup, transactionsQueryGroup } from './queryGroups';

// Invalidate multiple related queries at once
invalidateQueriesForKeys([
accountsQueryGroup.list.queryKey,
transactionsQueryGroup.list.queryKey,
]);

Common Use Cases​

1. Cascade Invalidation​

When deleting an account, invalidate all related entities:

export const accountsQueryGroup = {
...accountsQueryGroupCRUD,
remove: {
...accountsQueryGroupCRUD.remove,
invalidates: (id: Account['id']) => [
...accountsQueryGroupCRUD.remove.invalidates(id),
transactionsQueryGroup.all.queryKey,
recurrencesQueryGroup.all.queryKey,
transferencesQueryGroup.all.queryKey,
movementsQueryGroup.all.queryKey,
remindersQueryGroup.all.queryKey,
],
},
};

// In wrapper hook
export const useAccountDelete = ({ onSuccess, ...rest }) =>
generatedAccountDelete({
mutation: {
mutationKey: [accountsQueryGroup.remove.queryKey],
onSuccess: (data, variables, context) => {
const invalidationKeys = accountsQueryGroup.remove.invalidates(variables.id);

// Invalidate all related queries at once
invalidateQueriesForKeys(invalidationKeys);

onSuccess?.(data, variables, context);
},
...rest,
},
});

2. Manual Multi-Query Invalidation​

// After a bulk operation
const handleBulkUpdate = async () => {
await bulkUpdateAccounts(selectedIds);

// Invalidate multiple query groups
invalidateQueriesForKeys([
accountsQueryGroup.list.queryKey,
accountsQueryGroup.all.queryKey,
balancesQueryGroup.list.queryKey,
dashboardQueryGroup.summary.queryKey,
]);
};

3. With Invalidation Options​

import { invalidateQueriesForKeys } from 'src/queries';

// Only refetch queries that are currently being observed
invalidateQueriesForKeys(
[
accountsQueryGroup.list.queryKey,
transactionsQueryGroup.list.queryKey,
],
{
refetchType: 'active',
}
);

// Invalidate without refetching
invalidateQueriesForKeys(
[
accountsQueryGroup.list.queryKey,
],
{
refetchType: 'none',
}
);

Integration with Query Groups​

The function is designed to work with the invalidates property of query groups:

// Define cascade invalidation in query group
export const transactionQueryGroup = {
...createQueryGroupCRUD<Transaction['id']>('transactions'),
create: {
...transactionsQueryGroupCRUD.create,
invalidates: [
transactionsQueryGroupCRUD.create.invalidates,
accountsQueryGroup.list.queryKey,
balancesQueryGroup.list.queryKey,
],
},
};

// Use in wrapper hook
export const useTransactionCreate = ({ onSuccess, ...rest }) =>
generatedTransactionCreate({
mutation: {
mutationKey: [transactionQueryGroup.create.queryKey],
onSuccess: (data, variables, context) => {
transactionQueryGroup.create.normalize?.(data);

// Invalidate all related queries
invalidateQueriesForKeys(transactionQueryGroup.create.invalidates);

onSuccess?.(data, variables, context);
},
...rest,
},
});

Comparison with Direct invalidateQueries​

Without invalidateQueriesForKeys​

// Manual, verbose approach
queryClient.invalidateQueries({ queryKey: [accountsQueryGroup.list.queryKey] });
queryClient.invalidateQueries({ queryKey: [transactionsQueryGroup.list.queryKey] });
queryClient.invalidateQueries({ queryKey: [movementsQueryGroup.list.queryKey] });

With invalidateQueriesForKeys​

// Clean, declarative approach
invalidateQueriesForKeys([
accountsQueryGroup.list.queryKey,
transactionsQueryGroup.list.queryKey,
movementsQueryGroup.list.queryKey,
]);

Advanced Patterns​

Dynamic Invalidation Based on Response​

export const useAccountUpdate = ({ onSuccess, ...rest }) =>
generatedAccountUpdate({
mutation: {
onSuccess: (data, variables, context) => {
// Base invalidations
const keysToInvalidate = accountsQueryGroup.update.invalidates(data.id);

// Add conditional invalidations based on response
if (data.balanceChanged) {
keysToInvalidate.push(balancesQueryGroup.list.queryKey);
}

if (data.statusChanged) {
keysToInvalidate.push(dashboardQueryGroup.stats.queryKey);
}

invalidateQueriesForKeys(keysToInvalidate);

onSuccess?.(data, variables, context);
},
...rest,
},
});

Backend-Driven Invalidation​

When your backend tells you which entities were affected:

// Backend response includes affected entities
interface MutationResponse {
data: Transaction;
affectedEntities: string[]; // ['accounts', 'balances', 'movements']
}

// Map entity names to query keys
const ENTITY_TO_QUERY_KEY_MAP: Record<string, QueryKey<any>> = {
accounts: accountsQueryGroup.list.queryKey,
transactions: transactionsQueryGroup.list.queryKey,
balances: balancesQueryGroup.list.queryKey,
movements: movementsQueryGroup.list.queryKey,
};

export const useComplexMutation = ({ onSuccess, ...rest }) =>
generatedComplexMutation({
mutation: {
onSuccess: (response: MutationResponse, variables, context) => {
// Extract query keys from backend response
const keysToInvalidate = response.affectedEntities
.map((entity) => ENTITY_TO_QUERY_KEY_MAP[entity])
.filter(Boolean);

invalidateQueriesForKeys(keysToInvalidate);

onSuccess?.(response, variables, context);
},
...rest,
},
});

Error Handling​

The function filters out falsy values, so you can safely include conditional keys:

invalidateQueriesForKeys([
accountsQueryGroup.list.queryKey,
shouldInvalidateTransactions ? transactionsQueryGroup.list.queryKey : null,
isAdmin ? adminQueryGroup.dashboard.queryKey : null,
]);

// Falsy values are automatically filtered out

Performance Considerations​

Batch Invalidations​

Invalidating multiple queries at once is more efficient than calling invalidation separately:

// GOOD - Single function call
invalidateQueriesForKeys([key1, key2, key3]);

// LESS EFFICIENT - Multiple function calls
queryClient.invalidateQueries({ queryKey: [key1] });
queryClient.invalidateQueries({ queryKey: [key2] });
queryClient.invalidateQueries({ queryKey: [key3] });

Granular vs. Broad Invalidation​

// Granular: Only invalidate specific resource
invalidateQueriesForKeys([
accountsQueryGroup.detail.queryKey(accountId),
]);

// Broad: Invalidate all accounts
invalidateQueriesForKeys([
accountsQueryGroup.all.queryKey,
]);

// Choose based on mutation impact

Best Practices​

1. Use with Query Group invalidates​

Define invalidation logic in query groups, execute in hooks:

// In query group definition
export const accountsQueryGroup = {
remove: {
invalidates: (id) => [
{ entity: 'accounts', id },
{ entity: 'accounts', method: 'list' },
transactionsQueryGroup.all.queryKey,
],
},
};

// In wrapper hook
invalidateQueriesForKeys(accountsQueryGroup.remove.invalidates(id));

2. Combine with Normalize​

Use optimistic updates first, then invalidate for fresh data:

onSuccess: (data, variables, context) => {
// 1. Optimistic update
queryGroup.update.normalize?.(data);

// 2. Invalidate to fetch fresh data
invalidateQueriesForKeys(queryGroup.update.invalidates(data.id));
};

3. Document Cascading Invalidations​

When invalidating multiple entities, add comments explaining why:

export const accountsQueryGroup = {
remove: {
invalidates: (id: Account['id']) => [
...accountsQueryGroupCRUD.remove.invalidates(id),
transactionsQueryGroup.all.queryKey, // Transactions belong to accounts
movementsQueryGroup.all.queryKey, // Movements aggregate transactions
balancesQueryGroup.list.queryKey, // Balances depend on accounts
],
},
};

See Also​