Skip to main content

Invalidation

Invalidation is the process of marking cached queries as stale, causing them to refetch. Builder provides convenient methods for invalidating queries based on your endpoint definitions.

Invalidate Specific Query​

Invalidate a query with exact parameters:

const getUser = client.query({
method: 'GET',
url: '/users/$userId',
querySchema: z.object({
include: z.string().optional(),
}),
responseSchema: userSchema,
processResponse: (data) => data,
})

// Invalidate specific query
await getUser.invalidate(queryClient, {
urlParams: { userId: '123' },
params: { include: 'posts' },
})

Invalidate All Matching Queries​

Invalidate all queries for an endpoint (ignores query params):

// Invalidate all queries for this user (ignores query params)
await getUser.invalidateAll(queryClient, {
urlParams: { userId: '123' },
})

Using in Components​

After Mutation​

const updateUser = client.mutation({
method: 'PUT',
url: '/users/$userId',
requestSchema: userUpdateSchema,
responseSchema: userSchema,
processResponse: (data) => data,
useContext: () => ({ queryClient: useQueryClient() }),
onSuccess: (data, variables, context) => {
// Invalidate user query after update
getUser.invalidate(context.queryClient, {
urlParams: { userId: variables.urlParams.userId },
})
},
})

Manual Invalidation​

function UserProfile({ userId }: { userId: string }) {
const queryClient = useQueryClient()

const handleRefresh = async () => {
await getUser.invalidate(queryClient, {
urlParams: { userId },
})
}

return <button onClick={handleRefresh}>Refresh</button>
}

Invalidation Patterns​

After Create​

const createUser = client.mutation({
method: 'POST',
url: '/users',
requestSchema: userCreateSchema,
responseSchema: userSchema,
processResponse: (data) => data,
useContext: () => ({ queryClient: useQueryClient() }),
onSuccess: (data, variables, context) => {
// Invalidate users list
getUsers.invalidateAll(context.queryClient, {})
},
})

After Update​

const updateUser = client.mutation({
method: 'PUT',
url: '/users/$userId',
requestSchema: userUpdateSchema,
responseSchema: userSchema,
processResponse: (data) => data,
useContext: () => ({ queryClient: useQueryClient() }),
onSuccess: (data, variables, context) => {
// Invalidate specific user
getUser.invalidate(context.queryClient, {
urlParams: { userId: data.id },
})
// Invalidate users list
getUsers.invalidateAll(context.queryClient, {})
},
})

After Delete​

const deleteUser = client.mutation({
method: 'DELETE',
url: '/users/$userId',
responseSchema: z.object({ success: z.boolean() }),
processResponse: (data) => data,
useContext: () => ({ queryClient: useQueryClient() }),
onSuccess: (data, variables, context) => {
// Remove from cache
context.queryClient.removeQueries({
queryKey: getUser.queryKey.filterKey({
urlParams: { userId: variables.urlParams.userId },
}),
})
// Invalidate users list
getUsers.invalidateAll(context.queryClient, {})
},
})

Manual Invalidation​

Using Query Keys​

import { useQueryClient } from '@tanstack/react-query'

function UserProfile({ userId }: { userId: string }) {
const queryClient = useQueryClient()

const handleInvalidate = () => {
// Manual invalidation with filter key
queryClient.invalidateQueries({
queryKey: getUser.queryKey.filterKey({ urlParams: { userId } }),
})
}

return <button onClick={handleInvalidate}>Invalidate</button>
}

Invalidate Multiple Endpoints​

function UserProfile({ userId }: { userId: string }) {
const queryClient = useQueryClient()

const handleRefreshAll = async () => {
// Invalidate all user-related queries
await Promise.all([
getUser.invalidateAll(queryClient, { urlParams: { userId } }),
getUserPosts.invalidateAll(queryClient, { urlParams: { userId } }),
getUserSettings.invalidateAll(queryClient, { urlParams: { userId } }),
])
}

return <button onClick={handleRefreshAll}>Refresh All</button>
}

Common Patterns​

Invalidate on Window Focus​

function UserProfile({ userId }: { userId: string }) {
const queryClient = useQueryClient()

useEffect(() => {
const handleFocus = () => {
getUser.invalidate(queryClient, {
urlParams: { userId },
})
}

window.addEventListener('focus', handleFocus)
return () => window.removeEventListener('focus', handleFocus)
}, [userId, queryClient])

return <div>{/* ... */}</div>
}

Invalidate on Interval​

function UserProfile({ userId }: { userId: string }) {
const queryClient = useQueryClient()

useEffect(() => {
const interval = setInterval(() => {
getUser.invalidate(queryClient, {
urlParams: { userId },
})
}, 30000) // Every 30 seconds

return () => clearInterval(interval)
}, [userId, queryClient])

return <div>{/* ... */}</div>
}

Invalidate After Timeout​

const updateUser = client.mutation({
method: 'PUT',
url: '/users/$userId',
requestSchema: userUpdateSchema,
responseSchema: userSchema,
processResponse: (data) => data,
useContext: () => ({ queryClient: useQueryClient() }),
onSuccess: (data, variables, context) => {
// Invalidate after a delay
setTimeout(() => {
getUser.invalidate(context.queryClient, {
urlParams: { userId: data.id },
})
}, 1000)
},
})

Best Practices​

// ✅ Good - invalidate related queries
const updateUser = client.mutation({
// ...
onSuccess: (data, variables, context) => {
getUser.invalidate(context.queryClient, {
urlParams: { userId: data.id },
})
getUsers.invalidateAll(context.queryClient, {})
},
})

Use invalidateAll for Lists​

// ✅ Good - invalidate all list queries
const createUser = client.mutation({
// ...
onSuccess: (data, variables, context) => {
getUsers.invalidateAll(context.queryClient, {})
},
})

Remove Queries on Delete​

// ✅ Good - remove deleted items from cache
const deleteUser = client.mutation({
// ...
onSuccess: (data, variables, context) => {
context.queryClient.removeQueries({
queryKey: getUser.queryKey.filterKey({
urlParams: { userId: variables.urlParams.userId },
}),
})
},
})

Next Steps​