Tu Primera Query
Tu Primera Query
Section titled “Tu Primera Query”¡Es hora de crear tu primera query con TanStack Query! En esta sección aprenderás los conceptos fundamentales creando ejemplos prácticos paso a paso.
🎯 Concepto: ¿Qué es una Query?
Section titled “🎯 Concepto: ¿Qué es una Query?”Una query es una petición para obtener datos del servidor. En TanStack Query, cada query se identifica por:
- Query Key: Un identificador único (array o string)
- Query Function: La función que hace la petición al servidor
const { data, isLoading, error } = useQuery({ queryKey: ['posts'], // 👈 Identificador único queryFn: () => fetchPosts() // 👈 Función que obtiene los datos});
🚀 Tu Primera Query: Lista de Posts
Section titled “🚀 Tu Primera Query: Lista de Posts”Vamos a crear una query simple para obtener una lista de posts:
1. Componente Básico
Section titled “1. Componente Básico”import React from 'react';import { useQuery } from '@tanstack/react-query';
function PostsList() { const { data: posts, isLoading, error } = useQuery({ queryKey: ['posts'], queryFn: async () => { const response = await fetch('https://jsonplaceholder.typicode.com/posts'); if (!response.ok) { throw new Error('Error fetching posts'); } return response.json(); } });
if (isLoading) return <div>Cargando posts...</div>; if (error) return <div>Error: {error.message}</div>;
return ( <div> <h2>Lista de Posts</h2> {posts?.map(post => ( <div key={post.id} style={{ margin: '20px 0', padding: '10px', border: '1px solid #ccc' }}> <h3>{post.title}</h3> <p>{post.body}</p> </div> ))} </div> );}
export default PostsList;
2. Estados de la Query
Section titled “2. Estados de la Query”TanStack Query proporciona varios estados útiles:
function PostsList() { const { data: posts, // Los datos obtenidos isLoading, // Primera carga (sin datos en cache) isFetching, // Cualquier petición en chapter isError, // Si hay error error, // El objeto error isSuccess, // Si la petición fue exitosa refetch // Función para recargar manualmente } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });
// UI más granular basada en estados if (isLoading) return <div>🔄 Cargando por primera vez...</div>; if (isError) return <div>❌ Error: {error.message}</div>; if (!posts) return <div>📭 No hay posts disponibles</div>;
return ( <div> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <h2>Lista de Posts</h2> <button onClick={() => refetch()} disabled={isFetching}> {isFetching ? '🔄 Recargando...' : '🔄 Recargar'} </button> </div>
{posts.map(post => ( <div key={post.id}> <h3>{post.title}</h3> <p>{post.body}</p> </div> ))} </div> );}
🔑 Query Keys: La Clave del Cache
Section titled “🔑 Query Keys: La Clave del Cache”Keys Simples
Section titled “Keys Simples”// String simpleuseQuery({ queryKey: 'posts', // ⚠️ No recomendado queryFn: fetchPosts});
// Array (recomendado)useQuery({ queryKey: ['posts'], // ✅ Mejor práctica queryFn: fetchPosts});
Keys Complejas
Section titled “Keys Complejas”// Con parámetrosuseQuery({ queryKey: ['posts', { status: 'published' }], queryFn: () => fetchPosts({ status: 'published' })});
// Con ID específicouseQuery({ queryKey: ['post', postId], queryFn: () => fetchPost(postId)});
// Con múltiples parámetrosuseQuery({ queryKey: ['posts', { page: 1, limit: 10, search: 'react' }], queryFn: ({ queryKey }) => { const [_key, params] = queryKey; return fetchPosts(params); }});
📊 Query con Parámetros
Section titled “📊 Query con Parámetros”Vamos a crear una query que acepta parámetros:
import React, { useState } from 'react';import { useQuery } from '@tanstack/react-query';
function UserPosts() { const [userId, setUserId] = useState(1);
const { data: posts, isLoading, error, isFetching } = useQuery({ queryKey: ['posts', 'user', userId], queryFn: async () => { const response = await fetch( `https://jsonplaceholder.typicode.com/posts?userId=${userId}` ); if (!response.ok) { throw new Error('Error fetching user posts'); } return response.json(); }, // Solo ejecutar si userId existe enabled: !!userId });
return ( <div> <div> <label> Seleccionar Usuario: <select value={userId} onChange={(e) => setUserId(Number(e.target.value))} > {[1, 2, 3, 4, 5].map(id => ( <option key={id} value={id}>Usuario {id}</option> ))} </select> </label> {isFetching && <span> 🔄 Cargando...</span>} </div>
{isLoading ? ( <div>Cargando posts del usuario...</div> ) : error ? ( <div>Error: {error.message}</div> ) : ( <div> <h3>Posts del Usuario {userId}</h3> {posts?.map(post => ( <div key={post.id} style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5' }}> <h4>{post.title}</h4> <p>{post.body}</p> </div> ))} </div> )} </div> );}
export default UserPosts;
🛠️ Query Function Avanzada
Section titled “🛠️ Query Function Avanzada”Usando la Query Key
Section titled “Usando la Query Key”const { data } = useQuery({ queryKey: ['post', postId], queryFn: ({ queryKey }) => { const [_key, id] = queryKey; // Extraer parámetros del queryKey return fetch(`/api/posts/${id}`).then(res => res.json()); }});
Con Signal para Cancelación
Section titled “Con Signal para Cancelación”const { data } = useQuery({ queryKey: ['posts'], queryFn: async ({ signal }) => { const response = await fetch('/api/posts', { signal }); if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }});
Función Async/Await Completa
Section titled “Función Async/Await Completa”const fetchPostsWithDetails = async ({ queryKey, signal }) => { const [_key, filters] = queryKey;
try { const response = await fetch('/api/posts', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(filters), signal, // Para cancelación automática });
if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); }
const data = await response.json();
// Transformar datos si es necesario return data.map(post => ({ ...post, createdAt: new Date(post.createdAt), excerpt: post.body.substring(0, 100) + '...' })); } catch (error) { if (error.name === 'AbortError') { console.log('Query cancelled'); throw error; } throw new Error(`Failed to fetch posts: ${error.message}`); }};
function PostsWithDetails() { const { data: posts, isLoading, error } = useQuery({ queryKey: ['posts', { status: 'published' }], queryFn: fetchPostsWithDetails });
// ... resto del componente}
🎛️ Opciones de Query
Section titled “🎛️ Opciones de Query”Configuración Básica
Section titled “Configuración Básica”const { data } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts,
// Cache y actualización staleTime: 5 * 60 * 1000, // 5 minutos cacheTime: 10 * 60 * 1000, // 10 minutos
// Reintentos retry: 3, retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
// Refetch automático refetchOnWindowFocus: false, refetchOnReconnect: true, refetchInterval: 30000, // Cada 30 segundos
// Ejecución condicional enabled: true,});
Configuración Avanzada
Section titled “Configuración Avanzada”const { data } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts,
// Inicializar con datos initialData: [],
// Placeholder mientras carga placeholderData: [],
// Transformar datos select: (data) => data.filter(post => post.published),
// Callbacks onSuccess: (data) => { console.log('Posts cargados:', data.length); }, onError: (error) => { console.error('Error cargando posts:', error); },
// Estructura de datos structuralSharing: true,});
🧪 Ejemplo Completo: Dashboard de Posts
Section titled “🧪 Ejemplo Completo: Dashboard de Posts”import React, { useState } from 'react';import { useQuery } from '@tanstack/react-query';
const fetchPosts = async ({ queryKey }) => { const [_key, filters] = queryKey; const params = new URLSearchParams(filters);
const response = await fetch( `https://jsonplaceholder.typicode.com/posts?${params}` );
if (!response.ok) { throw new Error('Failed to fetch posts'); }
return response.json();};
function PostsDashboard() { const [filters, setFilters] = useState({}); const [selectedUserId, setSelectedUserId] = useState('');
const { data: posts = [], isLoading, error, isFetching, refetch } = useQuery({ queryKey: ['posts', filters], queryFn: fetchPosts, staleTime: 2 * 60 * 1000, // 2 minutos select: (data) => { // Transformar y filtrar datos return data .sort((a, b) => b.id - a.id) // Ordenar por ID descendente .map(post => ({ ...post, excerpt: post.body.substring(0, 100) + '...' })); } });
const handleFilterChange = (userId) => { setSelectedUserId(userId); setFilters(userId ? { userId } : {}); };
if (isLoading) { return ( <div style={{ textAlign: 'center', padding: '20px' }}> <div>🔄 Cargando posts...</div> </div> ); }
if (error) { return ( <div style={{ color: 'red', padding: '20px' }}> <h3>❌ Error al cargar posts</h3> <p>{error.message}</p> <button onClick={() => refetch()}>Reintentar</button> </div> ); }
return ( <div style={{ padding: '20px' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}> <h2>Dashboard de Posts {isFetching && '🔄'}</h2> <button onClick={() => refetch()} disabled={isFetching}> Actualizar </button> </div>
<div style={{ marginBottom: '20px' }}> <label> Filtrar por usuario: <select value={selectedUserId} onChange={(e) => handleFilterChange(e.target.value)} > <option value="">Todos los usuarios</option> {[1, 2, 3, 4, 5].map(id => ( <option key={id} value={id}>Usuario {id}</option> ))} </select> </label> </div>
<div> <p>📊 Total de posts: {posts.length}</p>
<div style={{ display: 'grid', gap: '15px' }}> {posts.map(post => ( <div key={post.id} style={{ border: '1px solid #ddd', borderRadius: '8px', padding: '15px', backgroundColor: '#f9f9f9' }} > <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '10px' }}> <strong>Post #{post.id}</strong> <span style={{ color: '#666' }}>Usuario {post.userId}</span> </div> <h3 style={{ margin: '10px 0' }}>{post.title}</h3> <p style={{ color: '#666' }}>{post.excerpt}</p> </div> ))} </div> </div> </div> );}
export default PostsDashboard;
✅ Mejores Prácticas para tu Primera Query
Section titled “✅ Mejores Prácticas para tu Primera Query”- Usa arrays para queryKey:
['posts']
en lugar de'posts'
- Maneja todos los estados:
isLoading
,error
,data
- Usa enabled para queries condicionales:
enabled: !!userId
- Transforma datos con select: Para filtrar o modificar la respuesta
- Implementa manejo de errores: Tanto en la UI como en la queryFn
- Usa AbortController: Para cancelación automática de peticiones
🎉 ¡Felicitaciones!
Section titled “🎉 ¡Felicitaciones!”¡Has creado tu primera query con TanStack Query! Ahora comprendes:
- Cómo estructurar una query básica
- La importancia de los query keys
- Cómo manejar diferentes estados
- Cómo usar parámetros en queries
- Opciones de configuración básicas
Ahora que dominas los fundamentos de las queries, es hora de explorar el hook más importante: useQuery
en detalle.
Próximo paso: useQuery