Skip to content

Configuración Inicial

Una vez instalado TanStack Query, es importante configurarlo correctamente para obtener el máximo rendimiento y una experiencia de desarrollo óptima.

🏗️ Estructura Recomendada del Proyecto

Section titled “🏗️ Estructura Recomendada del Proyecto”

Organiza tu proyecto de manera que sea fácil mantener y escalar:

src/
├── api/
│ ├── client.js # Cliente HTTP (axios, fetch)
│ ├── queries.js # Definiciones de queries
│ └── mutations.js # Definiciones de mutations
├── hooks/
│ ├── useUsers.js # Hooks personalizados
│ └── usePosts.js
├── utils/
│ └── queryClient.js # Configuración del QueryClient
└── App.jsx

⚙️ Configuración Avanzada del QueryClient

Section titled “⚙️ Configuración Avanzada del QueryClient”

1. Crear un archivo de configuración separado

Section titled “1. Crear un archivo de configuración separado”
src/utils/queryClient.js
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
// Configuración para queries
staleTime: 5 * 60 * 1000, // 5 minutos
cacheTime: 10 * 60 * 1000, // 10 minutos
retry: (failureCount, error) => {
// No reintentar errores del cliente (4xx)
if (error?.response?.status >= 400 && error?.response?.status < 500) {
return false;
}
// Reintentar hasta 3 veces para errores del servidor
return failureCount < 3;
},
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
refetchOnWindowFocus: false,
refetchOnReconnect: true,
},
mutations: {
// Configuración para mutations
retry: 1,
retryDelay: 1000,
},
},
});
// Configuración de error global
queryClient.setMutationDefaults(['posts'], {
mutationFn: async ({ id, ...data }) => {
const response = await fetch(`/api/posts/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error('Error updating post');
}
return response.json();
},
});
src/App.jsx
import React from 'react';
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { queryClient } from './utils/queryClient';
import Router from './Router';
function App() {
return (
<QueryClientProvider client={queryClient}>
<Router />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
export default App;
src/api/client.js
import axios from 'axios';
export const apiClient = axios.create({
baseURL: process.env.REACT_APP_API_URL || 'https://jsonplaceholder.typicode.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// Interceptor para requests
apiClient.interceptors.request.use(
(config) => {
// Agregar token de autenticación si existe
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Interceptor para responses
apiClient.interceptors.response.use(
(response) => response,
(error) => {
// Manejar errores globalmente
if (error.response?.status === 401) {
// Redirigir a login
localStorage.removeItem('authToken');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
src/api/client.js
const BASE_URL = process.env.REACT_APP_API_URL || 'https://jsonplaceholder.typicode.com';
class APIClient {
constructor(baseURL = BASE_URL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
};
// Agregar token si existe
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
get(endpoint) {
return this.request(endpoint);
}
post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data),
});
}
put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data),
});
}
delete(endpoint) {
return this.request(endpoint, {
method: 'DELETE',
});
}
}
export const apiClient = new APIClient();

1. Definir Query Keys de forma consistente

Section titled “1. Definir Query Keys de forma consistente”
src/api/queryKeys.js
export const queryKeys = {
// Users
users: ['users'],
user: (id) => ['users', id],
userPosts: (userId) => ['users', userId, 'posts'],
// Posts
posts: ['posts'],
post: (id) => ['posts', id],
postComments: (postId) => ['posts', postId, 'comments'],
// Con filtros
postsFiltered: (filters) => ['posts', { filters }],
usersSearch: (query) => ['users', 'search', { query }],
};
src/api/queries.js
import { apiClient } from './client';
import { queryKeys } from './queryKeys';
// Users API
export const usersAPI = {
getAll: () => apiClient.get('/users'),
getById: (id) => apiClient.get(`/users/${id}`),
create: (userData) => apiClient.post('/users', userData),
update: (id, userData) => apiClient.put(`/users/${id}`, userData),
delete: (id) => apiClient.delete(`/users/${id}`),
};
// Posts API
export const postsAPI = {
getAll: (params = {}) => {
const searchParams = new URLSearchParams(params);
return apiClient.get(`/posts?${searchParams}`);
},
getById: (id) => apiClient.get(`/posts/${id}`),
getByUserId: (userId) => apiClient.get(`/posts?userId=${userId}`),
create: (postData) => apiClient.post('/posts', postData),
update: (id, postData) => apiClient.put(`/posts/${id}`, postData),
delete: (id) => apiClient.delete(`/posts/${id}`),
};
src/hooks/useUsers.js
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { usersAPI } from '../api/queries';
import { queryKeys } from '../api/queryKeys';
export function useUsers() {
return useQuery({
queryKey: queryKeys.users,
queryFn: usersAPI.getAll,
});
}
export function useUser(id) {
return useQuery({
queryKey: queryKeys.user(id),
queryFn: () => usersAPI.getById(id),
enabled: !!id, // Solo ejecutar si id existe
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: usersAPI.create,
onSuccess: () => {
// Invalidar lista de usuarios
queryClient.invalidateQueries({ queryKey: queryKeys.users });
},
});
}
export function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, ...userData }) => usersAPI.update(id, userData),
onSuccess: (data, { id }) => {
// Invalidar usuario específico y lista
queryClient.invalidateQueries({ queryKey: queryKeys.user(id) });
queryClient.invalidateQueries({ queryKey: queryKeys.users });
},
});
}
src/App.jsx
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* Tu aplicación */}
{/* DevTools con configuración personalizada */}
<ReactQueryDevtools
initialIsOpen={false}
position="bottom-right"
toggleButtonProps={{
style: {
marginLeft: '5px',
transform: undefined,
position: 'fixed',
bottom: '10px',
right: '10px',
zIndex: 99999,
},
}}
/>
</QueryClientProvider>
);
}

Configura variables de entorno para diferentes ambientes:

.env.development
REACT_APP_API_URL=http://localhost:3001/api
REACT_APP_ENABLE_DEVTOOLS=true
# .env.production
REACT_APP_API_URL=https://api.miapp.com
REACT_APP_ENABLE_DEVTOOLS=false
src/App.jsx
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* Tu aplicación */}
{/* DevTools solo en desarrollo */}
{process.env.REACT_APP_ENABLE_DEVTOOLS === 'true' && (
<ReactQueryDevtools initialIsOpen={false} />
)}
</QueryClientProvider>
);
}
src/App.jsx
import { useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
function App() {
const queryClient = useQueryClient();
useEffect(() => {
// Prefetch datos críticos al cargar la app
queryClient.prefetchQuery({
queryKey: queryKeys.users,
queryFn: usersAPI.getAll,
staleTime: 10 * 60 * 1000, // 10 minutos
});
}, [queryClient]);
return (
// Tu aplicación
);
}
src/App.jsx
import { Suspense } from 'react';
function App() {
return (
<QueryClientProvider client={queryClient}>
<Suspense fallback={<div>Cargando aplicación...</div>}>
<Router />
</Suspense>
<ReactQueryDevtools />
</QueryClientProvider>
);
}
  • Crear QueryClient con configuración personalizada
  • Configurar cliente HTTP (axios/fetch)
  • Definir query keys consistentes
  • Crear funciones API reutilizables
  • Implementar hooks personalizados
  • Configurar DevTools apropiadamente
  • Establecer variables de entorno
  • Implementar manejo de errores global
  • Configurar prefetching si es necesario

¡Perfecto! Ahora tienes una configuración sólida de TanStack Query. Es hora de crear tu primera query y ver todo en acción.

Próximo paso: Tu Primera Query