tutoriales.com

React Query: Gestión Eficiente de Datos Asíncronos y Caché en React

Este tutorial te guiará a través de React Query (TanStack Query), una potente biblioteca para la gestión de datos asíncronos en aplicaciones React. Aprenderás a optimizar la carga, el caching y la sincronización con el servidor, mejorando significativamente el rendimiento y la experiencia de usuario.

Intermedio20 min de lectura7 views23 de marzo de 2026Reportar error

🚀 Introducción a React Query (TanStack Query)

En el desarrollo de aplicaciones web modernas con React, la gestión de datos asíncronos es una tarea omnipresente. Desde la obtención de información de una API REST hasta la mutación de datos en el servidor, los componentes de React a menudo se llenan de lógica compleja para manejar estados de carga, errores, éxito, y lo que es más importante, la sincronización de esos datos. Tradicionalmente, esto ha involucrado useState, useEffect, y una gran cantidad de código boilerplate.

Aquí es donde entra en juego React Query (ahora parte de TanStack Query), una biblioteca que simplifica enormemente la forma en que manejamos los datos asíncronos. No es un gestor de estado global como Redux, sino más bien una librería de fetching de datos. React Query te ayuda a:

  • Eliminar el código boilerplate: Olvídate de isLoading, isError, data, error gestionados manualmente en cada componente.
  • Cachar datos: Mantiene los datos frescos y evita peticiones innecesarias al servidor.
  • Sincronizar datos: Asegura que tu UI refleje el estado más reciente del servidor.
  • Optimizar el rendimiento: Evita re-renders innecesarios y proporciona una UX fluida.
  • Manejar actualizaciones optimistas: Mejora la percepción de velocidad en la UI.

Este tutorial te proporcionará una comprensión profunda y práctica de React Query, desde su configuración básica hasta características avanzadas.


🛠️ Configuración Inicial de un Proyecto React con React Query

Antes de sumergirnos en la magia de React Query, necesitamos un proyecto React funcional y la biblioteca instalada. Si ya tienes un proyecto, puedes omitir el primer paso.

Paso 1: Crear un Proyecto React (si es necesario)

Usaremos Vite para un inicio rápido:

npm create vite@latest my-react-query-app --template react-ts
cd my-react-query-app
npm install

Paso 2: Instalar React Query

npm install @tanstack/react-query

Paso 3: Configurar QueryClientProvider

Para que React Query funcione en nuestra aplicación, necesitamos envolver nuestra aplicación con el QueryClientProvider. Este proveedor es el responsable de mantener la caché de consultas y proporcionar el contexto necesario a todos los hooks de React Query.

Modifica tu archivo src/main.tsx (o src/index.js si no usas TypeScript):

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

// Crea una nueva instancia de QueryClient
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutos de 'stale' por defecto
      cacheTime: 1000 * 60 * 10, // 10 minutos de caché por defecto (garbage collection)
      refetchOnWindowFocus: true, // Por defecto refetch cuando la ventana recupera el foco
    },
  },
});

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
);
📌 Nota: Los `defaultOptions` en `QueryClient` son muy útiles para establecer un comportamiento global para todas tus consultas. Por ejemplo, `staleTime` y `cacheTime` son fundamentales para el caching.

Conceptos clave de Caching:

  • staleTime: El tiempo que los datos se consideran "frescos". Mientras los datos son frescos, no se realizará ninguna petición de red. Una vez que los datos se vuelven stale, se marcarán para ser refetch si el componente se monta o la ventana recupera el foco.
  • cacheTime: El tiempo que los datos inactivos (sin componentes suscritos) permanecen en caché antes de ser eliminados por el garbage collector. Por defecto, son 5 minutos. Si un componente se suscribe a los datos antes de que expire cacheTime, los datos se rehidratarán.
Componente monta / Fetch ¿Estado de los Datos? (Verifica staleTime) Fresh Usa datos de Caché Sin peticiones de red Stale Refetch en fondo Actualiza Caché Sin suscriptores (Componente desmontado) Expira cacheTime/gcTime Garbage Collected

✨ Tu Primera Consulta: useQuery

El hook useQuery es el caballo de batalla de React Query. Se utiliza para obtener datos del servidor. Vamos a crear un componente simple que liste algunos posts de una API pública.

Crearemos un archivo src/App.tsx (o modifica el existente):

import React from 'react';
import { useQuery } from '@tanstack/react-query';

interface Post {
  id: number;
  title: string;
  body: string;
  userId: number;
}

async function fetchPosts(): Promise<Post[]> {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  if (!res.ok) {
    throw new Error('Network response was not ok');
  }
  return res.json();
}

function App() {
  // useQuery recibe dos argumentos principales:
  // 1. Una 'query key' (array): Identificador único para esta consulta.
  // 2. Una 'query function' (función asíncrona): La lógica para obtener los datos.
  const { data, isLoading, isError, error, refetch, isRefetching } = useQuery<
    Post[],
    Error
  >({
    queryKey: ['posts'],
    queryFn: fetchPosts,
  });

  if (isLoading) {
    return <div class="progress-bar"><div class="progress-fill" style="width: 100%; background: #007bff;">Cargando posts...</div></div>;
  }

  if (isError) {
    return (
      <div class="callout warning">
        ⚠️ <strong>Error al cargar:</strong> {error?.message}
        <button onClick={() => refetch()} disabled={isRefetching}>
          {isRefetching ? 'Reintentando...' : 'Reintentar'}
        </button>
      </div>
    );
  }

  return (
    <div style={{ padding: '20px' }}>
      <h1>✨ Nuestros Posts</h1>
      {isRefetching && <span class="badge yellow">Actualizando en segundo plano...</span>}
      <button onClick={() => refetch()} disabled={isRefetching} style={{ marginBottom: '20px', marginLeft: '10px' }}>
        Recargar Posts
      </button>
      <ul>
        {data?.map((post) => (
          <li key={post.id} style={{ marginBottom: '10px' }}>
            <strong>{post.title}</strong>
            <p>{post.body.substring(0, 100)}...</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

💡 Explicación de useQuery

  • queryKey: ['posts']: Esta es la clave única que React Query usa para identificar y cacheadear los datos de esta consulta. Es un array y puede contener strings, números y objetos para construir claves más complejas (ej. ['posts', userId]).
  • queryFn: fetchPosts: La función asíncrona que realiza la petición de red y devuelve los datos. React Query la llamará automáticamente.
  • data: Los datos obtenidos de la queryFn una vez que la consulta es exitosa.
  • isLoading: Un booleano que es true mientras la consulta se está realizando por primera vez.
  • isError: Un booleano que es true si la consulta falla.
  • error: El objeto de error si la consulta falla.
  • refetch: Una función que puedes llamar para forzar una nueva petición de datos.
  • isRefetching: Es true si se está realizando un refetch en segundo plano (después de la carga inicial).
💡 Consejo: Usa `isFetching` en lugar de `isLoading` si quieres mostrar un estado de carga para todas las peticiones, incluyendo los refetches en segundo plano. `isLoading` solo es `true` para la primera carga.

🔑 Query Keys: La Base del Caching y la Invalidez

Las query keys son fundamentales en React Query. Son la forma en que React Query identifica y organiza la caché de datos. Una queryKey es siempre un array. La serialización de las queryKey es consistente, lo que significa que ['todos', { status: 'done' }] es lo mismo que ['todos', { status: 'done' }], pero diferente de ['todos', { status: 'pending' }].

Tipos de Query Keys

  1. Arrays de Strings simples: ['posts']
  2. Arrays con variables: ['post', postId]
function PostDetail({ postId }: { postId: number }) {
const { data } = useQuery({
queryKey: ['post', postId], // La clave cambiará si postId cambia
queryFn: () => fetch(`/api/posts/${postId}`).then(res => res.json())
});
// ...
}
  1. Arrays con objetos para filtros: ['todos', { status: 'done', userId: 1 }]
function TodoList({ status, userId }: { status: string; userId: number }) {
const { data } = useQuery({
queryKey: ['todos', { status, userId }],
queryFn: () => fetch(`/api/todos?status=${status}&userId=${userId}`).then(res => res.json())
});
// ...
}
🔥 Importante: Si cualquier parte de tu `queryKey` cambia, React Query tratará la consulta como una consulta completamente nueva y la ejecutará de nuevo (a menos que ya haya datos cacheados para esa nueva clave).

📝 Mutaciones de Datos con useMutation

Mientras que useQuery es para obtener datos, useMutation se usa para crear, actualizar o eliminar datos en el servidor (operaciones POST, PUT, DELETE). useMutation no cacha los resultados de la misma manera que useQuery, ya que las mutaciones suelen tener efectos secundarios en el servidor.

Vamos a añadir un formulario para crear nuevos posts.

Modifica src/App.tsx para incluir el siguiente componente de formulario y añadirlo al App principal:

import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// ... (Post interface y fetchPosts function como antes)

async function createPost(newPost: Omit<Post, 'id'>): Promise<Post> {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(newPost),
  });
  if (!res.ok) {
    throw new Error('Network response for create was not ok');
  }
  return res.json();
}

function PostForm() {
  const queryClient = useQueryClient();
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');

  const { mutate, isLoading, isError, error, isSuccess } = useMutation<Post, Error, Omit<Post, 'id'>>({
    mutationFn: createPost,
    onSuccess: () => {
      // Invalida la caché de 'posts' para que se refetch cuando sea necesario
      queryClient.invalidateQueries({ queryKey: ['posts'] });
      setTitle('');
      setBody('');
      alert('Post creado con éxito!');
    },
    onError: (err) => {
      alert(`Error al crear post: ${err.message}`);
    },
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!title || !body) return;
    mutate({ title, body, userId: 1 }); // Suponemos userId 1 para el ejemplo
  };

  return (
    <form onSubmit={handleSubmit} style={{ border: '1px solid #ccc', padding: '20px', borderRadius: '8px', marginBottom: '30px' }}>
      <h3>📝 Crear Nuevo Post</h3>
      <div>
        <label htmlFor="title">Título:</label>
        <input
          id="title"
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          disabled={isLoading}
          style={{ width: '100%', padding: '8px', margin: '5px 0' }}
        />
      </div>
      <div>
        <label htmlFor="body">Contenido:</label>
        <textarea
          id="body"
          value={body}
          onChange={(e) => setBody(e.target.value)}
          disabled={isLoading}
          rows={4}
          style={{ width: '100%', padding: '8px', margin: '5px 0' }}
        ></textarea>
      </div>
      <button type="submit" disabled={isLoading || !title || !body}>
        {isLoading ? 'Creando...' : 'Guardar Post'}
      </button>
      {isError && <div class="callout warning">⚠️ Error al enviar: {error?.message}</div>}
      {isSuccess && <div class="callout tip">✅ Post creado.</div>}
    </form>
  );
}

function App() {
  // ... (useQuery como antes)

  return (
    <div style={{ padding: '20px' }}>
      <PostForm /> {/* Añadimos el formulario aquí */}
      {/* ... Resto del componente App ... */}
    </div>
  );
}

export default App;

💡 Explicación de useMutation

  • mutationFn: createPost: La función asíncrona que realiza la petición al servidor para crear, actualizar o eliminar datos.
  • mutate: La función que se invoca para disparar la mutación, pasándole los datos necesarios.
  • isLoading: true mientras la mutación está en curso.
  • onSuccess: Un callback que se ejecuta cuando la mutación es exitosa. Aquí es donde normalmente se invalidateQueries para que React Query sepa que los datos relacionados podrían haber cambiado y deben ser refetched.
  • onError: Un callback que se ejecuta si la mutación falla.
🔥 Importante: El `queryClient.invalidateQueries` es crucial. Le dice a React Query que los datos asociados a la clave `['posts']` están obsoletos (`stale`) y deben ser recargados la próxima vez que se acceda a ellos o cuando la ventana recupere el foco. Esto asegura que la lista de posts en la UI se actualice automáticamente después de crear uno nuevo.

🔄 Invalidez de Caché y Refetching

Una de las características más potentes de React Query es su capacidad para gestionar la invalidez de la caché, asegurando que tu UI siempre muestre datos frescos. Cuando una mutación altera datos en el servidor, no queremos que nuestra UI muestre datos antiguos.

queryClient.invalidateQueries

Como vimos en useMutation, invalidateQueries es el método principal para marcar una consulta o un grupo de consultas como stale. Una vez stale, React Query intentará recargar esos datos la próxima vez que un componente suscrito los solicite (o inmediatamente si refetchOnMount o refetchOnWindowFocus están activos y los datos son stale).

// Invalida todos los queries que contengan 'posts' en su clave
queryClient.invalidateQueries({ queryKey: ['posts'] });

// Invalida un query específico
queryClient.invalidateQueries({ queryKey: ['post', 123] });

// Invalida todos los queries que comiencen con 'todos'
queryClient.invalidateQueries({ queryKey: ['todos'] });

// Invalida todos los queries de cualquier tipo (usar con precaución)
queryClient.invalidateQueries();

queryClient.refetchQueries

Aunque invalidateQueries es el más común, refetchQueries es útil cuando quieres forzar un refetch inmediato de una consulta (o un grupo de consultas) sin esperar a que se vuelvan stale o a que un componente se monte/enfoque.

// Refetch inmediatamente todos los queries que contengan 'posts'
queryClient.refetchQueries({ queryKey: ['posts'] });
📌 Nota: Generalmente, es mejor usar `invalidateQueries` para permitir que React Query decida el momento óptimo para el refetch, a menos que tengas una razón específica para forzar un refetch inmediato.

📈 Optimistic Updates: Mejorando la UX

Las actualizaciones optimistas son una técnica poderosa para mejorar la experiencia de usuario. En lugar de esperar a que una mutación en el servidor se complete para actualizar la UI, actualizamos la UI inmediatamente, asumiendo que la mutación será exitosa. Si la mutación falla, revertimos el cambio.

Esto hace que la aplicación se sienta mucho más rápida y receptiva.

Vamos a implementar una actualización optimista para cuando un post se crea. En lugar de invalidar y esperar, lo añadiremos a la lista inmediatamente.

import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// ... (Post interface, fetchPosts function, createPost function como antes)

function PostFormOptimistic() {
  const queryClient = useQueryClient();
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');

  const { mutate, isLoading, isError, error, isSuccess } = useMutation<Post, Error, Omit<Post, 'id'>>({
    mutationFn: createPost,
    onMutate: async (newPost) => {
      // 1. Cancela cualquier refetch pendiente para la lista de posts
      //    para evitar que nuestro update optimista sea sobrescrito
      await queryClient.cancelQueries({ queryKey: ['posts'] });

      // 2. Guarda el estado actual de la caché de posts
      const previousPosts = queryClient.getQueryData<Post[]>(['posts']);

      // 3. Actualiza optimísticamente la caché con el nuevo post
      queryClient.setQueryData<Post[]>(['posts'], (oldPosts) => {
        // Generar un ID temporal para el nuevo post
        const tempId = Math.floor(Math.random() * -1000000); 
        return oldPosts ? [...oldPosts, { ...newPost, id: tempId }] : [{ ...newPost, id: tempId }];
      });

      // 4. Devuelve un contexto con los datos previos para 'onError'
      return { previousPosts };
    },
    onSuccess: (data, variables, context) => {
      // Una vez que el servidor confirma, podemos invalidar para obtener el ID real
      queryClient.invalidateQueries({ queryKey: ['posts'] });
      setTitle('');
      setBody('');
      alert('Post creado con éxito (optimistic)!');
    },
    onError: (err, newPost, context) => {
      // Si la mutación falla, revierte a los datos previos
      if (context?.previousPosts) {
        queryClient.setQueryData<Post[]>(['posts'], context.previousPosts);
      }
      alert(`Error optimista al crear post: ${err.message}`);
    },
    onSettled: () => {
      // Siempre refetch después de que la mutación se ha asentado (éxito o error)
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!title || !body) return;
    mutate({ title, body, userId: 1 });
  };

  return (
    <form onSubmit={handleSubmit} style={{ border: '1px solid #ccc', padding: '20px', borderRadius: '8px', marginBottom: '30px' }}>
      <h3>📝 Crear Nuevo Post (Optimista)</h3>
      {/* ... (inputs y botón como en PostForm) ... */}
       <div>
        <label htmlFor="title">Título:</label>
        <input
          id="title"
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          disabled={isLoading}
          style={{ width: '100%', padding: '8px', margin: '5px 0' }}
        />
      </div>
      <div>
        <label htmlFor="body">Contenido:</label>
        <textarea
          id="body"
          value={body}
          onChange={(e) => setBody(e.target.value)}
          disabled={isLoading}
          rows={4}
          style={{ width: '100%', padding: '8px', margin: '5px 0' }}
        ></textarea>
      </div>
      <button type="submit" disabled={isLoading || !title || !body}>
        {isLoading ? 'Creando Optimista...' : 'Guardar Post Optimista'}
      </button>
      {isError && <div class="callout warning">⚠️ Error al enviar: {error?.message}</div>}
      {isSuccess && <div class="callout tip">✅ Post creado (optimista).</div>}
    </form>
  );
}

// ... (Añadir PostFormOptimistic en App en lugar de PostForm si quieres probarlo)

export default App;

Flujo de Actualización Optimista

Paso 1 (`onMutate`): Se cancelan los refetches pendientes para la query afectada para evitar conflictos. Se guarda una "instantánea" del estado actual de la caché. Se actualiza la caché localmente con los datos nuevos (temporalmente).
Paso 2 (Llamada al Servidor): La `mutationFn` se ejecuta para enviar los datos al servidor.
Paso 3a (`onSuccess`): Si la mutación tiene éxito, se invalida la query para que React Query la refetchee en segundo plano y obtenga los datos reales del servidor (incluyendo IDs reales, timestamps, etc.).
Paso 3b (`onError`): Si la mutación falla, se usa la "instantánea" guardada en `onMutate` para revertir la caché a su estado anterior.
Paso 4 (`onSettled`): Se ejecuta siempre, independientemente del resultado. Aquí se suele invalidar la query para asegurar la consistencia.
🔥 Importante: Las actualizaciones optimistas pueden ser complejas, especialmente si la lógica de negocio del servidor es intrincada. Úsalas cuando el impacto en la UX sea significativo y el riesgo de conflicto bajo.

🌍 Herramientas de Desarrollo (Devtools)

React Query viene con unas excelentes herramientas de desarrollo que te permiten visualizar la caché, el estado de las queries y las mutaciones. Son imprescindibles para depurar y comprender cómo funciona tu aplicación con React Query.

Instalación:

npm install @tanstack/react-query-devtools

Uso en src/main.tsx (o src/App.tsx):

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; // Importa los Devtools

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
      {/* Añade los Devtools. Se mostrarán solo en desarrollo por defecto. */}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  </React.StrictMode>,
);

Al iniciar tu aplicación, verás un pequeño icono flotante en la esquina de la pantalla. Haz clic en él para abrir los Devtools y explora las diferentes pestañas:

  • Queries: Lista de todas las consultas activas e inactivas en la caché, con sus claves, estados (stale, fresh), y datos.
  • Mutations: Lista de mutaciones recientes y su estado.
  • Cache: Visualización de toda la caché de React Query.
💡 Consejo: Usa `initialIsOpen={false}` para que los Devtools no se abran automáticamente al cargar la página, o `initialIsOpen={true}` durante la depuración activa. También puedes usar `position='bottom-right'` para cambiar su ubicación.

📌 Patrones Avanzados y Buenas Prácticas

1. Reintentos (Retries) y Exponential Backoff

React Query reintenta automáticamente las consultas fallidas por defecto. Puedes configurar esto globalmente o por consulta:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 3, // Reintenta 3 veces antes de fallar
      retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30 * 1000) // Backoff exponencial
    },
  },
});

// O por consulta:
useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,
  retry: 5,
  retryDelay: 5000 // Espera 5 segundos entre reintentos
});

2. Prefetching de Datos

Para mejorar la UX, puedes prefetch datos antes de que el usuario los necesite. Esto es útil para páginas a las que el usuario probablemente navegará después.

// En un componente, por ejemplo, al pasar el ratón por un enlace
function PostLink({ postId }: { postId: number }) {
  const queryClient = useQueryClient();

  const handleMouseEnter = () => {
    queryClient.prefetchQuery({
      queryKey: ['post', postId],
      queryFn: () => fetch(`/api/posts/${postId}`).then(res => res.json()),
    });
  };

  return (
    <a href={`/posts/${postId}`} onMouseEnter={handleMouseEnter}>
      Ver Post {postId}
    </a>
  );
}

3. Infinite Queries (useInfiniteQuery)

Para listas con paginación infinita (scroll infinito), useInfiniteQuery es la herramienta ideal. Permite cargar páginas adicionales de datos y concatenarlas en una sola estructura.

Ejemplo Básico de useInfiniteQuery
import { useInfiniteQuery } from '@tanstack/react-query';

interface Page {
  posts: Post[];
  nextCursor: number | undefined;
}

async function fetchPaginatedPosts({ pageParam = 0 }): Promise<Page> {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_start=${pageParam}&_limit=10`);
  const posts: Post[] = await res.json();
  const nextCursor = posts.length < 10 ? undefined : pageParam + 10;
  return { posts, nextCursor };
}

function InfinitePosts() {
  const { 
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
    isLoading,
    isError,
    error,
  } = useInfiniteQuery<Page, Error>(
    {
      queryKey: ['infinitePosts'],
      queryFn: fetchPaginatedPosts,
      initialPageParam: 0,
      getNextPageParam: (lastPage) => lastPage.nextCursor,
    }
  );

  if (isLoading) return <div>Cargando posts infinitos...</div>;
  if (isError) return <div>Error: {error?.message}</div>;

  return (
    <div>
      <h2>Posts Infinitos</h2>
      {data?.pages.map((page, i) => (
        <React.Fragment key={i}>
          {page.posts.map((post) => (
            <p key={post.id}>{post.title}</p>
          ))}
        </React.Fragment>
      ))}
      <button
        onClick={() => fetchNextPage()}
        disabled={!hasNextPage || isFetchingNextPage}
      >
        {isFetchingNextPage
          ? 'Cargando más...'
          : hasNextPage
          ? 'Cargar Más'
          : 'No hay más posts'}
      </button>
    </div>
  );
}

// ... (Añadir InfinitePosts en App si quieres probarlo)

4. Selectores de Query

Los selectores te permiten transformar o seleccionar solo una parte de los datos de una consulta después de que han sido cacheados, sin forzar una nueva petición ni un re-render del componente si la parte seleccionada no ha cambiado.

interface User {
  id: number;
  name: string;
  email: string;
}

function UserName({ userId }: { userId: number }) {
  const { data: userName } = useQuery<User, Error, string>({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
    select: (user) => user.name, // Solo seleccionamos el nombre
  });

  return <div>Usuario: {userName}</div>;
}

Esto evita que el componente UserName se re-renderice si el email del usuario cambia, pero el name permanece igual.


✅ Conclusión y Próximos Pasos

React Query es una biblioteca transformadora para el manejo de datos asíncronos en React. Al externalizar la compleja lógica de fetching, caching, sincronización y manejo de errores, te permite escribir código más limpio, mantenible y robusto. Hemos cubierto los fundamentos:

  • Configuración (QueryClientProvider)
  • Obtención de datos (useQuery)
  • Mutaciones (useMutation)
  • Invalidez de caché (invalidateQueries)
  • Actualizaciones optimistas
  • Devtools
  • Patrones avanzados como prefetching y useInfiniteQuery.

La clave para dominar React Query reside en comprender el ciclo de vida de los datos, el papel de las query keys y cómo las opciones de configuración (staleTime, cacheTime) impactan el comportamiento de la caché. Te animo a que sigas experimentando con esta poderosa herramienta y explores su documentación oficial para descubrir todas sus capacidades.


💡 Consejo final: La documentación de TanStack Query es excelente y muy completa. Siempre que tengas una duda o quieras profundizar, ¡es tu mejor recurso!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!