Skip to content

useQueryClient

useQueryClient te da acceso directo al cliente de queries, permitiendo control programático sobre el cache, invalidación de queries, y manipulación de datos. Es esencial para casos avanzados y optimizaciones.

import { useQueryClient } from '@tanstack/react-query';
function MyComponent() {
const queryClient = useQueryClient();
// Ahora tienes acceso a todos los métodos del cliente
return (
<div>
<button onClick={() => queryClient.invalidateQueries()}>
Invalidar todas las queries
</button>
</div>
);
}

La invalidación marca queries como “stale” y las refetch si están activas:

function RefreshButton() {
const queryClient = useQueryClient();
const handleRefresh = () => {
// Invalidar todas las queries
queryClient.invalidateQueries();
// Invalidar queries específicas
queryClient.invalidateQueries({ queryKey: ['posts'] });
// Invalidar queries que coincidan con un patrón
queryClient.invalidateQueries({ queryKey: ['posts', 'user'] });
};
return (
<button onClick={handleRefresh}>
🔄 Actualizar datos
</button>
);
}
function SmartRefresh() {
const queryClient = useQueryClient();
const refreshUserData = (userId) => {
// Invalidar todas las queries relacionadas con un usuario
queryClient.invalidateQueries({
queryKey: ['user', userId]
});
queryClient.invalidateQueries({
queryKey: ['posts', 'user', userId]
});
};
const refreshStaleData = () => {
// Solo invalidar queries que están marcadas como stale
queryClient.invalidateQueries({
refetchType: 'active', // solo queries activa
stale: true // solo las que están stale
});
};
const refreshPostsOnly = () => {
// Invalidar queries que empiecen con ['posts']
queryClient.invalidateQueries({
queryKey: ['posts'],
exact: false // incluir queries que empiecen con esta key
});
};
return (
<div>
<button onClick={() => refreshUserData(123)}>
Actualizar Usuario 123
</button>
<button onClick={refreshStaleData}>
Actualizar Solo Datos Obsoletos
</button>
<button onClick={refreshPostsOnly}>
Actualizar Todos los Posts
</button>
</div>
);
}
function CacheReader() {
const queryClient = useQueryClient();
const getUserFromCache = (userId) => {
// Obtener datos específicos del cache
const userData = queryClient.getQueryData(['user', userId]);
if (userData) {
console.log('Usuario en cache:', userData);
} else {
console.log('Usuario no está en cache');
}
};
const getAllCachedPosts = () => {
// Obtener todas las queries que coincidan
const queries = queryClient.getQueriesData({
queryKey: ['posts']
});
console.log('Posts en cache:', queries);
};
const checkQueryState = (queryKey) => {
// Obtener estado completo de una query
const queryState = queryClient.getQueryState(queryKey);
console.log('Estado de la query:', {
data: queryState?.data,
isStale: queryState?.isStale,
isLoading: queryState?.isLoading,
lastUpdated: queryState?.dataUpdatedAt
});
};
return (
<div>
<button onClick={() => getUserFromCache(1)}>
Leer Usuario 1 del Cache
</button>
<button onClick={getAllCachedPosts}>
Ver Posts en Cache
</button>
<button onClick={() => checkQueryState(['user', 1])}>
Estado de Query Usuario
</button>
</div>
);
}
function CacheWriter() {
const queryClient = useQueryClient();
const updateUserInCache = (userId, newData) => {
// Actualizar datos específicos
queryClient.setQueryData(['user', userId], newData);
// Actualizar usando función (como setState)
queryClient.setQueryData(['user', userId], (oldData) => ({
...oldData,
...newData,
updatedAt: new Date().toISOString()
}));
};
const addPostToList = (newPost) => {
// Agregar un post a la lista existente
queryClient.setQueryData(['posts'], (oldPosts = []) => {
return [newPost, ...oldPosts];
});
};
const updatePostInList = (postId, updates) => {
// Actualizar un post específico en la lista
queryClient.setQueryData(['posts'], (oldPosts = []) => {
return oldPosts.map(post =>
post.id === postId
? { ...post, ...updates }
: post
);
});
};
const removePostFromList = (postId) => {
// Remover un post de la lista
queryClient.setQueryData(['posts'], (oldPosts = []) => {
return oldPosts.filter(post => post.id !== postId);
});
};
return (
<div>
<button onClick={() => updateUserInCache(1, { name: 'Nuevo Nombre' })}>
Actualizar Usuario en Cache
</button>
<button onClick={() => addPostToList({ id: Date.now(), title: 'Nuevo Post' })}>
Agregar Post al Cache
</button>
</div>
);
}

Cargar datos antes de que se necesiten:

function DataPrefetcher() {
const queryClient = useQueryClient();
const prefetchUser = async (userId) => {
// Prefetch datos si no están en cache
await queryClient.prefetchQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 10 * 60 * 1000, // 10 minutos
});
};
const prefetchUserPosts = async (userId) => {
// Prefetch condicionalmente
const existingData = queryClient.getQueryData(['posts', 'user', userId]);
if (!existingData) {
await queryClient.prefetchQuery({
queryKey: ['posts', 'user', userId],
queryFn: () => fetchUserPosts(userId),
});
}
};
const prefetchOnHover = async (userId) => {
// Prefetch when user hovers over a link
await queryClient.prefetchQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000,
});
};
return (
<div>
<button onClick={() => prefetchUser(1)}>
Precargar Usuario 1
</button>
<div
onMouseEnter={() => prefetchOnHover(2)}
style={{ padding: '10px', border: '1px solid #ccc', margin: '10px' }}
>
Hover para precargar Usuario 2
</div>
</div>
);
}
function QueryCanceller() {
const queryClient = useQueryClient();
const cancelAllQueries = () => {
// Cancelar todas las queries en chapter
queryClient.cancelQueries();
};
const cancelSpecificQuery = () => {
// Cancelar queries específicas
queryClient.cancelQueries({ queryKey: ['posts'] });
};
const cancelUserQueries = (userId) => {
// Cancelar todas las queries de un usuario
queryClient.cancelQueries({ queryKey: ['user', userId] });
queryClient.cancelQueries({ queryKey: ['posts', 'user', userId] });
};
return (
<div>
<button onClick={cancelAllQueries}>
❌ Cancelar Todas las Queries
</button>
<button onClick={cancelSpecificQuery}>
❌ Cancelar Queries de Posts
</button>
</div>
);
}
function CacheCleaner() {
const queryClient = useQueryClient();
const removeUserFromCache = (userId) => {
// Remover query específica
queryClient.removeQueries({ queryKey: ['user', userId] });
};
const clearExpiredQueries = () => {
// Remover queries inactivas (garbage collection manual)
queryClient.removeQueries({
type: 'inactive',
stale: true
});
};
const clearAllCache = () => {
// Limpiar todo el cache
queryClient.clear();
};
return (
<div>
<button onClick={() => removeUserFromCache(1)}>
🗑️ Remover Usuario 1 del Cache
</button>
<button onClick={clearExpiredQueries}>
🧹 Limpiar Queries Expiradas
</button>
<button onClick={clearAllCache}>
💥 Limpiar Todo el Cache
</button>
</div>
);
}
function CacheInspector() {
const queryClient = useQueryClient();
const inspectCache = () => {
// Obtener todas las queries en cache
const cache = queryClient.getQueryCache();
const queries = cache.getAll();
console.log('Total queries en cache:', queries.length);
queries.forEach(query => {
console.log({
queryKey: query.queryKey,
data: query.state.data,
isStale: query.isStale(),
isActive: query.isActive(),
lastUpdated: new Date(query.state.dataUpdatedAt)
});
});
};
const getCacheStats = () => {
const cache = queryClient.getQueryCache();
const allQueries = cache.getAll();
const stats = {
total: allQueries.length,
active: allQueries.filter(q => q.isActive()).length,
stale: allQueries.filter(q => q.isStale()).length,
inactive: allQueries.filter(q => !q.isActive()).length
};
console.log('Estadísticas del cache:', stats);
return stats;
};
return (
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
<h3>🔍 Inspector de Cache</h3>
<button onClick={inspectCache}>
Ver Contenido del Cache
</button>
<button onClick={getCacheStats}>
Ver Estadísticas
</button>
</div>
);
}
import { useState, useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
function CacheDashboard() {
const queryClient = useQueryClient();
const [cacheData, setCacheData] = useState([]);
const [stats, setStats] = useState({});
const updateCacheData = () => {
const cache = queryClient.getQueryCache();
const queries = cache.getAll();
const queriesData = queries.map(query => ({
id: query.queryHash,
queryKey: JSON.stringify(query.queryKey),
isActive: query.isActive(),
isStale: query.isStale(),
hasData: !!query.state.data,
dataUpdatedAt: query.state.dataUpdatedAt,
error: query.state.error?.message,
observers: query.observers.length
}));
setCacheData(queriesData);
setStats({
total: queries.length,
active: queries.filter(q => q.isActive()).length,
stale: queries.filter(q => q.isStale()).length,
withData: queries.filter(q => q.state.data).length,
withErrors: queries.filter(q => q.state.error).length
});
};
useEffect(() => {
updateCacheData();
// Actualizar cada 2 segundos
const interval = setInterval(updateCacheData, 2000);
return () => clearInterval(interval);
}, [queryClient]);
const handleInvalidateAll = () => {
queryClient.invalidateQueries();
setTimeout(updateCacheData, 100);
};
const handleClearCache = () => {
queryClient.clear();
setTimeout(updateCacheData, 100);
};
const handleRemoveInactive = () => {
queryClient.removeQueries({ type: 'inactive' });
setTimeout(updateCacheData, 100);
};
return (
<div style={{ padding: '20px', fontFamily: 'monospace' }}>
<h2>📊 Dashboard de Cache TanStack Query</h2>
{/* Estadísticas */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(5, 1fr)',
gap: '10px',
marginBottom: '20px'
}}>
<div style={{ padding: '10px', backgroundColor: '#f0f0f0', textAlign: 'center' }}>
<strong>{stats.total}</strong><br/>Total
</div>
<div style={{ padding: '10px', backgroundColor: '#d4edda', textAlign: 'center' }}>
<strong>{stats.active}</strong><br/>Activas
</div>
<div style={{ padding: '10px', backgroundColor: '#fff3cd', textAlign: 'center' }}>
<strong>{stats.stale}</strong><br/>Obsoletas
</div>
<div style={{ padding: '10px', backgroundColor: '#d1ecf1', textAlign: 'center' }}>
<strong>{stats.withData}</strong><br/>Con Datos
</div>
<div style={{ padding: '10px', backgroundColor: '#f8d7da', textAlign: 'center' }}>
<strong>{stats.withErrors}</strong><br/>Con Errores
</div>
</div>
{/* Controles */}
<div style={{ marginBottom: '20px' }}>
<button
onClick={handleInvalidateAll}
style={{
padding: '8px 16px',
backgroundColor: '#ffc107',
border: 'none',
borderRadius: '4px',
marginRight: '10px'
}}
>
🔄 Invalidar Todas
</button>
<button
onClick={handleRemoveInactive}
style={{
padding: '8px 16px',
backgroundColor: '#6c757d',
color: 'white',
border: 'none',
borderRadius: '4px',
marginRight: '10px'
}}
>
🧹 Remover Inactivas
</button>
<button
onClick={handleClearCache}
style={{
padding: '8px 16px',
backgroundColor: '#dc3545',
color: 'white',
border: 'none',
borderRadius: '4px'
}}
>
💥 Limpiar Todo
</button>
</div>
{/* Lista de queries */}
<div>
<h3>Queries en Cache ({cacheData.length})</h3>
<div style={{ maxHeight: '400px', overflowY: 'auto' }}>
{cacheData.map(query => (
<div
key={query.id}
style={{
padding: '10px',
margin: '5px 0',
border: '1px solid #ddd',
borderRadius: '4px',
backgroundColor: query.isActive ? '#d4edda' : '#f8f9fa'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<strong>{query.queryKey}</strong>
<div>
{query.isActive && <span style={{ color: 'green' }}></span>}
{query.isStale && <span style={{ color: 'orange' }}></span>}
{query.error && <span style={{ color: 'red' }}></span>}
</div>
</div>
<div style={{ fontSize: '12px', color: '#666', marginTop: '5px' }}>
Observadores: {query.observers} |
Datos: {query.hasData ? '' : ''} |
Actualizado: {query.dataUpdatedAt ? new Date(query.dataUpdatedAt).toLocaleTimeString() : 'Nunca'}
</div>
{query.error && (
<div style={{ fontSize: '12px', color: 'red', marginTop: '5px' }}>
Error: {query.error}
</div>
)}
</div>
))}
</div>
</div>
</div>
);
}
export default CacheDashboard;
function PostMutations() {
const queryClient = useQueryClient();
const { mutate: createPost } = useMutation({
mutationFn: createPostAPI,
onSuccess: (newPost) => {
// Método 1: Invalidar y refetch
queryClient.invalidateQueries({ queryKey: ['posts'] });
// Método 2: Actualizar cache directamente
queryClient.setQueryData(['posts'], (oldPosts) => [newPost, ...oldPosts]);
// Método 3: Agregar a cache si la query existe
const existingPosts = queryClient.getQueryData(['posts']);
if (existingPosts) {
queryClient.setQueryData(['posts'], [newPost, ...existingPosts]);
}
}
});
}
function UserList() {
const queryClient = useQueryClient();
const handleUserHover = (userId) => {
// Prefetch detalles del usuario al hacer hover
queryClient.prefetchQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000
});
};
return (
<div>
{users.map(user => (
<div
key={user.id}
onMouseEnter={() => handleUserHover(user.id)}
>
<Link to={`/users/${user.id}`}>{user.name}</Link>
</div>
))}
</div>
);
}

✅ Mejores Prácticas para useQueryClient

Section titled “✅ Mejores Prácticas para useQueryClient”
  1. Usa invalidateQueries después de mutaciones: Para mantener datos sincronizados
  2. Prefetch datos relacionados: Antes de que el usuario los necesite
  3. Actualiza cache directamente: Para actualizaciones optimistas
  4. Limpia cache periódicamente: Para evitar uso excesivo de memoria
  5. Usa setQueryData con cuidado: Asegúrate de mantener la estructura correcta
  6. Cancela queries cuando sea necesario: Para evitar actualizaciones innecesarias
  7. Inspecciona el cache en desarrollo: Para debugging y optimización
  8. Maneja estados inconsistentes: Cuando manipulas cache directamente
  9. Usa exact: false: Para invalidar grupos de queries relacionadas
  10. Implementa garbage collection: Para queries que ya no se usan

¡Perfecto! Ahora dominas el control avanzado del cache. Continuemos con useInfiniteQuery para paginación infinita.

Próximo paso: useInfiniteQuery