useQueryClient
useQueryClient: Control Total del Cache
Section titled “useQueryClient: Control Total del Cache”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.
🎯 Obteniendo el Query Client
Section titled “🎯 Obteniendo el Query Client”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> );}
🔄 Invalidación de Queries
Section titled “🔄 Invalidación de Queries”La invalidación marca queries como “stale” y las refetch si están activas:
Invalidación Básica
Section titled “Invalidación Básica”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> );}
Invalidación Selectiva
Section titled “Invalidación Selectiva”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> );}
📊 Manipulación Directa del Cache
Section titled “📊 Manipulación Directa del Cache”Leer Datos del Cache
Section titled “Leer Datos del Cache”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> );}
Escribir Datos al Cache
Section titled “Escribir Datos al Cache”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> );}
🚀 Prefetching de Datos
Section titled “🚀 Prefetching de Datos”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> );}
🎛️ Gestión Avanzada de Queries
Section titled “🎛️ Gestión Avanzada de Queries”Cancelar Queries
Section titled “Cancelar Queries”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> );}
Remover Queries del Cache
Section titled “Remover Queries del Cache”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> );}
🔍 Utilidades de Cache
Section titled “🔍 Utilidades de Cache”Inspector de Cache
Section titled “Inspector de Cache”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> );}
🎨 Ejemplo Completo: Dashboard de Cache
Section titled “🎨 Ejemplo Completo: Dashboard de Cache”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;
🎯 Patrones Comunes con useQueryClient
Section titled “🎯 Patrones Comunes con useQueryClient”1. Sincronización después de Mutaciones
Section titled “1. Sincronización después de Mutaciones”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]); } } });}
2. Prefetch basado en Interacciones
Section titled “2. Prefetch basado en Interacciones”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”- Usa invalidateQueries después de mutaciones: Para mantener datos sincronizados
- Prefetch datos relacionados: Antes de que el usuario los necesite
- Actualiza cache directamente: Para actualizaciones optimistas
- Limpia cache periódicamente: Para evitar uso excesivo de memoria
- Usa setQueryData con cuidado: Asegúrate de mantener la estructura correcta
- Cancela queries cuando sea necesario: Para evitar actualizaciones innecesarias
- Inspecciona el cache en desarrollo: Para debugging y optimización
- Maneja estados inconsistentes: Cuando manipulas cache directamente
- Usa exact: false: Para invalidar grupos de queries relacionadas
- 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