Configuración Inicial
Configuración Inicial
Section titled “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”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 globalqueryClient.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(); },});
2. Integrar en tu aplicación
Section titled “2. Integrar en tu aplicación”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;
🌐 Configuración del Cliente HTTP
Section titled “🌐 Configuración del Cliente HTTP”Con Axios
Section titled “Con Axios”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 requestsapiClient.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 responsesapiClient.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); });
Con Fetch (nativo)
Section titled “Con Fetch (nativo)”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();
📊 Organización de Queries y Mutations
Section titled “📊 Organización de Queries y Mutations”1. Definir Query Keys de forma consistente
Section titled “1. Definir Query Keys de forma consistente”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 }],};
2. Crear funciones de API reutilizables
Section titled “2. Crear funciones de API reutilizables”import { apiClient } from './client';import { queryKeys } from './queryKeys';
// Users APIexport 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 APIexport 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}`),};
3. Crear hooks personalizados
Section titled “3. Crear hooks personalizados”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 }); }, });}
🔧 Configuración de DevTools Avanzada
Section titled “🔧 Configuración de DevTools Avanzada”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> );}
🌍 Variables de Entorno
Section titled “🌍 Variables de Entorno”Configura variables de entorno para diferentes ambientes:
REACT_APP_API_URL=http://localhost:3001/apiREACT_APP_ENABLE_DEVTOOLS=true
# .env.productionREACT_APP_API_URL=https://api.miapp.comREACT_APP_ENABLE_DEVTOOLS=false
function App() { return ( <QueryClientProvider client={queryClient}> {/* Tu aplicación */}
{/* DevTools solo en desarrollo */} {process.env.REACT_APP_ENABLE_DEVTOOLS === 'true' && ( <ReactQueryDevtools initialIsOpen={false} /> )} </QueryClientProvider> );}
🚀 Optimizaciones de Performance
Section titled “🚀 Optimizaciones de Performance”1. Prefetching de datos críticos
Section titled “1. Prefetching de datos críticos”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 );}
2. Configuración de Suspense (opcional)
Section titled “2. Configuración de Suspense (opcional)”import { Suspense } from 'react';
function App() { return ( <QueryClientProvider client={queryClient}> <Suspense fallback={<div>Cargando aplicación...</div>}> <Router /> </Suspense> <ReactQueryDevtools /> </QueryClientProvider> );}
✅ Checklist de Configuración
Section titled “✅ Checklist de Configuración”- 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