tutoriales.com

Consumiendo APIs REST con React: Guía Completa de Fetch, Axios y React Query

Este tutorial exhaustivo te guiará a través de las diferentes maneras de interactuar con APIs REST desde una aplicación React. Cubriremos la Fetch API nativa, la popular librería Axios y la potente solución React Query para una gestión de estado y rendimiento óptimos.

Intermedio15 min de lectura9 views
Reportar error

🚀 Introducción al Consumo de APIs REST en React

En el mundo del desarrollo web moderno, las aplicaciones de frontend como las construidas con React rara vez viven en aislamiento. La gran mayoría necesitan interactuar con servicios backend para obtener, enviar, actualizar o eliminar datos. Estas interacciones se realizan típicamente a través de APIs REST (Representational State Transfer), que actúan como un puente entre tu aplicación React y la base de datos o lógica de negocio en el servidor.

Consumir una API REST implica realizar peticiones HTTP (GET, POST, PUT, DELETE, etc.) a endpoints específicos y manejar las respuestas. En React, hay varias herramientas y enfoques para lograr esto, cada uno con sus propias ventajas y casos de uso.

En este tutorial, exploraremos los métodos más comunes y efectivos:

  • Fetch API: La interfaz nativa del navegador para hacer peticiones de red.
  • Axios: Una popular librería de terceros basada en promesas para hacer peticiones HTTP.
  • React Query (TanStack Query): Una librería moderna y avanzada para la gestión de estado del servidor en React, que abstrae y optimiza el consumo de APIs.

¿Por qué es crucial entender esto? 🤔

El manejo eficiente de datos remotos es fundamental para construir aplicaciones React rápidas, robustas y con una excelente experiencia de usuario. Una mala gestión puede llevar a problemas de rendimiento, errores, y una interfaz de usuario inconsistente.


🛠️ Herramientas para Consumir APIs REST en React

Antes de sumergirnos en los ejemplos, veamos un resumen de las herramientas que analizaremos:

HerramientaTipoCurva de AprendizajeCaracterísticas DestacadasCasos de Uso
Fetch APINativa del navegadorBajaLigera, no necesita instalación, basada en Promesas.Proyectos pequeños, peticiones sencillas.
AxiosLibreríaMediaInterceptores, cancelación de peticiones, transformación automática JSON.Proyectos medianos/grandes, necesidad de funcionalidades avanzadas.
React QueryLibreríaAlta (inicial)Caching, re-validación, sincronización, gestión de errores, paginación.Aplicaciones complejas con mucha gestión de datos remotos.
💡 Consejo: La elección de la herramienta dependerá de la complejidad de tu aplicación y de tus requisitos específicos para la gestión de datos.

1. Consumiendo APIs con Fetch API 🌐

La Fetch API es la forma moderna y nativa de hacer peticiones de red en los navegadores web. Está basada en Promesas, lo que facilita el manejo asíncrono de las respuestas.

1.1 Petición GET Básica

Para obtener datos, usaremos el método GET. Aquí te mostramos cómo hacer una petición simple para obtener una lista de publicaciones desde una API de prueba:

import React, { useState, useEffect } from 'react';

function PostsList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then(response => {
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.json();
      })
      .then(data => {
        setPosts(data);
        setLoading(false);
      })
      .catch(error => {
        setError(error);
        setLoading(false);
      });
  }, []); // El array vacío asegura que se ejecute solo una vez al montar el componente

  if (loading) return <div>Cargando publicaciones...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>Publicaciones</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.body}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default PostsList;
📌 Nota: Es una buena práctica manejar los estados de carga, error y éxito para proporcionar *feedback* al usuario.

1.2 Petición POST con Fetch

Para enviar datos al servidor, utilizamos el método POST. Necesitarás configurar el method a 'POST' y el body con los datos que quieres enviar, generalmente en formato JSON. También es crucial establecer el encabezado Content-Type a application/json.

import React, { useState } from 'react';

function CreatePostForm() {
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const [message, setMessage] = useState('');
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setMessage('Enviando...');
    setError(null);

    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ title, body, userId: 1 }), // userId es un ejemplo para la API de prueba
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const data = await response.json();
      setMessage(`Publicación creada con éxito: ${data.id}`);
      setTitle('');
      setBody('');
    } catch (error) {
      setError(error);
      setMessage('Error al crear la publicación.');
    }
  };

  return (
    <div>
      <h1>Crear Nueva Publicación</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label>Título:</label>
          <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} required />
        </div>
        <div>
          <label>Cuerpo:</label>
          <textarea value={body} onChange={(e) => setBody(e.target.value)} required />
        </div>
        <button type="submit">Crear Publicación</button>
      </form>
      {message && <p>{message}</p>}
      {error && <p style={{ color: 'red' }}>Error: {error.message}</p>}
    </div>
  );
}

export default CreatePostForm;

1.3 Ventajas y Desventajas de Fetch API

Ventajas
  • Nativa del navegador: No hay que instalar nada, reduciendo el tamaño del bundle.
  • Basada en Promesas: Manejo asíncrono moderno.
  • Suficiente para peticiones simples: Ideal para necesidades básicas.
Desventajas
  • Manejo de errores: No rechaza la promesa en errores HTTP (4xx, 5xx), solo en errores de red. Hay que verificar response.ok.
  • Configuración repetitiva: Encabezados, timeouts y otras opciones deben configurarse manualmente en cada petición.
  • Sin interceptores: No hay una forma sencilla de interceptar peticiones o respuestas globalmente.
  • Manejo de JSON: response.json() es asíncrono, lo que añade un .then() adicional.

2. Consumiendo APIs con Axios ✨

Axios es una librería HTTP basada en Promesas muy popular y ampliamente utilizada en el ecosistema de React. Ofrece una API más consistente y algunas características adicionales que Fetch no tiene de forma nativa.

Para usar Axios, primero debes instalarlo:

npm install axios
# o
yarn add axios

2.1 Petición GET Básica con Axios

Axios simplifica la forma de hacer peticiones GET y maneja automáticamente la transformación JSON de las respuestas.

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function AxiosPostsList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    axios.get('https://jsonplaceholder.typicode.com/posts')
      .then(response => {
        setPosts(response.data);
        setLoading(false);
      })
      .catch(error => {
        setError(error);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>Cargando publicaciones con Axios...</div>;
  if (error) return <div>Error Axios: {error.message}</div>;

  return (
    <div>
      <h1>Publicaciones (Axios)</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.body}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default AxiosPostsList;
🔥 Importante: Axios rechaza la promesa automáticamente si el código de estado HTTP es 4xx o 5xx, lo que simplifica el manejo de errores en comparación con Fetch. Los datos de la respuesta se encuentran en `response.data`.

2.2 Petición POST con Axios

El envío de datos con Axios también es más conciso. Simplemente pasas el objeto de datos como segundo argumento al método post.

import React, { useState } from 'react';
import axios from 'axios';

function AxiosCreatePostForm() {
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const [message, setMessage] = useState('');
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setMessage('Enviando...');
    setError(null);

    try {
      const response = await axios.post('https://jsonplaceholder.typicode.com/posts', {
        title, 
        body, 
        userId: 1 
      });

      setMessage(`Publicación creada con éxito (Axios): ${response.data.id}`);
      setTitle('');
      setBody('');
    } catch (error) {
      setError(error);
      setMessage('Error al crear la publicación con Axios.');
    }
  };

  return (
    <div>
      <h1>Crear Nueva Publicación (Axios)</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label>Título:</label>
          <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} required />
        </div>
        <div>
          <label>Cuerpo:</label>
          <textarea value={body} onChange={(e) => setBody(e.target.value)} required />
        </div>
        <button type="submit">Crear Publicación</button>
      </form>
      {message && <p>{message}</p>}
      {error && <p style={{ color: 'red' }}>Error: {error.message}</p>}
    </div>
  );
}

export default AxiosCreatePostForm;

2.3 Interceptores de Axios (Configuración Global) 🛡️

Una de las características más potentes de Axios son los interceptores. Permiten interceptar peticiones o respuestas antes de que sean manejadas por .then() o .catch(). Esto es útil para:

  • Añadir tokens de autenticación a todas las peticiones.
  • Manejar errores globalmente (ej. redirigir en caso de 401).
  • Registrar peticiones/respuestas.
// En un archivo separado, por ejemplo, axiosConfig.js
import axios from 'axios';

const instance = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com',
  timeout: 5000,
  headers: {'X-Custom-Header': 'foobar'}
});

// Interceptor de peticiones
instance.interceptors.request.use(
  config => {
    // console.log('Petición enviada:', config);
    const token = localStorage.getItem('authToken');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

// Interceptor de respuestas
instance.interceptors.response.use(
  response => {
    // console.log('Respuesta recibida:', response);
    return response;
  },
  error => {
    if (error.response && error.response.status === 401) {
      // Redirigir al login, refrescar token, etc.
      console.log('Token expirado o no autorizado. Redirigiendo...');
      // window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export default instance;

Luego, usarías instance en lugar de axios en tus componentes:

import axiosInstance from './axiosConfig'; // Tu instancia configurada

// ... dentro de tu componente
useEffect(() => {
  axiosInstance.get('/posts')
    .then(response => setPosts(response.data))
    .catch(error => setError(error));
}, []);

2.4 Ventajas y Desventajas de Axios

Ventajas
  • Manejo de errores: Rechaza promesas para errores HTTP.
  • Interceptores: Personalización global de peticiones/respuestas.
  • Transformación automática de JSON: No necesita response.json().
  • Cancelación de peticiones: Útil para evitar condiciones de carrera o peticiones innecesarias.
  • API consistente: Métodos dedicados para cada tipo de petición ( .get(), .post(), etc.).
Desventajas
  • Librería externa: Añade peso al bundle (aunque mínimo).
  • No nativo: Requiere instalación.

3. Consumiendo APIs con React Query (TanStack Query) 🚀

Pro Si estás construyendo aplicaciones React complejas que dependen mucho de datos remotos, React Query (ahora parte de TanStack Query) es un cambio de juego. No es solo una librería para hacer peticiones HTTP; es una potente herramienta para la gestión del estado del servidor (server state).

React Query se encarga de:

  • Caching: Guarda las respuestas de la API para que tu UI se actualice instantáneamente en futuras peticiones.
  • Re-validación: Re-valida datos automáticamente en segundo plano para asegurar que la UI siempre muestre datos frescos.
  • Sincronización: Mantiene tus datos actualizados entre diferentes componentes y pestañas.
  • Actualizaciones optimistas: Permite que la UI se actualice antes de que la API responda, mejorando la percepción de velocidad.
  • Paginación y Infinite Scrolling: Simplifica la implementación de estas características complejas.
  • Manejo de errores y estados de carga: Abstrae gran parte de la lógica repetitiva.

Primero, instala React Query:

npm install @tanstack/react-query
# o
yarn add @tanstack/react-query

3.1 Configuración Inicial

Necesitas envolver tu aplicación con un QueryClientProvider para que los hooks de React Query estén disponibles.

// src/index.js o App.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; // Herramientas de desarrollo

const queryClient = new QueryClient();

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
      <ReactQueryDevtools initialIsOpen={false} /> {/* Opcional: para depuración */}
    </QueryClientProvider>
  </React.StrictMode>
);
React Application QueryClientProvider React Component (Listado) React Component (Detalle) QueryClient API / Backend Cache useQuery useMutation Fetch Data Persistencia

3.2 Petición GET con useQuery

El hook useQuery es el núcleo de React Query para obtener datos. Recibe una clave de consulta (queryKey) y una función de consulta (queryFn).

import React from 'react';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios'; // Se puede usar Axios o Fetch dentro de queryFn

const fetchPosts = async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/posts');
  return data;
};

function ReactQueryPostsList() {
  const { 
    data: posts, 
    isLoading, 
    isError, 
    error,
    isFetching // Indica si una consulta está en proceso de re-validación/carga
  } = useQuery({ 
    queryKey: ['posts'], // Clave única para esta consulta
    queryFn: fetchPosts, // Función que realiza la petición API
    staleTime: 5 * 60 * 1000, // Los datos se consideran "frescos" durante 5 minutos
    cacheTime: 10 * 60 * 1000 // Los datos se guardan en caché durante 10 minutos (luego se eliminan)
  });

  if (isLoading) return <div>Cargando publicaciones con React Query...</div>;
  if (isError) return <div>Error React Query: {error.message}</div>;

  return (
    <div>
      <h1>Publicaciones (React Query) {isFetching ? '🔄' : ''}</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.body}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ReactQueryPostsList;
💡 Consejo: La clave de consulta (`queryKey`) es fundamental para el caching y la re-validación. Si los parámetros de tu API cambian (ej. ID de usuario), la clave debe reflejarlo, por ejemplo `['post', postId]`.

3.3 Petición POST con useMutation

Para modificar datos en el servidor, React Query proporciona el hook useMutation. Esto es ideal para peticiones POST, PUT, DELETE, etc.

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

const createPost = async (newPost) => {
  const { data } = await axios.post('https://jsonplaceholder.typicode.com/posts', newPost);
  return data;
};

function ReactQueryCreatePostForm() {
  const queryClient = useQueryClient(); // Para invalidar/actualizar la caché
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');

  const mutation = useMutation({ 
    mutationFn: createPost,
    onSuccess: () => {
      // Invalida la caché de 'posts' para que se vuelva a cargar al siguiente GET
      queryClient.invalidateQueries({ queryKey: ['posts'] }); 
      // También puedes actualizar la caché directamente con setQueryData
      // queryClient.setQueryData(['posts'], (oldPosts) => [...oldPosts, newPostFromServer]);
      setTitle('');
      setBody('');
    },
    onError: (error) => {
      console.error('Error al crear post:', error);
    }
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    mutation.mutate({ title, body, userId: 1 });
  };

  return (
    <div>
      <h1>Crear Nueva Publicación (React Query)</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label>Título:</label>
          <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} required />
        </div>
        <div>
          <label>Cuerpo:</label>
          <textarea value={body} onChange={(e) => setBody(e.target.value)} required />
        </div>
        <button type="submit" disabled={mutation.isLoading}>
          {mutation.isLoading ? 'Creando...' : 'Crear Publicación'}
        </button>
      </form>
      {mutation.isSuccess && <p>Publicación creada con éxito: ID {mutation.data.id}</p>}
      {mutation.isError && <p style={{ color: 'red' }}>Error: {mutation.error.message}</p>}
    </div>
  );
}

export default ReactQueryCreatePostForm;
⚠️ Advertencia: Para `useMutation`, `mutationFn` solo debe recibir **un argumento**. Si necesitas pasar múltiples valores, agrúpalos en un objeto.

3.4 Ventajas y Desventajas de React Query

Ventajas
  • Gestión de estado del servidor: Abstrae completamente el caching, la re-validación, la sincronización y la actualización de datos.
  • Rendimiento: Reduce drásticamente las peticiones a la API y mejora la velocidad percibida.
  • Menos código boilerplate: Elimina la necesidad de useState y useEffect para el manejo de estados de carga/error.
  • Experiencia de desarrollo: Las herramientas de desarrollo (ReactQueryDevtools) facilitan la depuración y optimización.
  • Escalabilidad: Ideal para aplicaciones con muchas interacciones de datos.
Desventajas
  • Curva de aprendizaje inicial: Requiere entender sus conceptos (claves de consulta, mutaciones, stale time, cache time).
  • Overhead para proyectos pequeños: Puede ser excesivo para aplicaciones muy simples con pocas peticiones.
  • Tamaño del bundle: Añade más peso que Axios o Fetch.

📈 Comparativa y Cuándo Usar Cada Herramienta

La elección entre Fetch, Axios y React Query depende en gran medida de los requisitos y la escala de tu proyecto.

Fetch API
Axios
React Query
  • Fetch API (Nivel Básico):

    • Cuándo usar: Proyectos muy pequeños, prototipos rápidos, cuando la simplicidad y el menor tamaño de bundle son críticos, o cuando solo necesitas hacer una o dos peticiones GET básicas.
    • No usar: Aplicaciones grandes con interacciones complejas, donde el manejo de errores y la configuración global se vuelven tediosos.
  • Axios (Nivel Intermedio):

    • Cuándo usar: La mayoría de las aplicaciones medianas a grandes. Cuando necesitas interceptores, cancelación de peticiones, una API más cómoda y un manejo de errores más robusto que Fetch.
    • No usar: Si tu aplicación es extremadamente simple y quieres evitar dependencias, o si ya estás manejando complejidades de estado del servidor con otras librerías (como Redux Thunk/Saga con lógica custom para caching, etc.). Si tienes muchas peticiones y quieres optimizar el rendimiento y la experiencia de desarrollo a otro nivel, React Query es superior.
  • React Query (Nivel Avanzado):

    • Cuándo usar: Aplicaciones complejas y ricas en datos donde la experiencia de usuario y el rendimiento son prioritarios. Cuando quieres eliminar el boilerplate de useState/useEffect para el manejo de carga/error/caching y tener una solución completa para el estado del servidor.
    • No usar: Proyectos extremadamente pequeños o para aprender los fundamentos de React, ya que su abstracción podría ocultar cómo funcionan las peticiones HTTP a bajo nivel.

✅ Buenas Prácticas y Consejos Adicionales

Independientemente de la herramienta que elijas, estas son algunas buenas prácticas generales:

  1. Manejo de Estados de Carga y Error: Siempre proporciona feedback visual al usuario cuando los datos se están cargando o si ha ocurrido un error. Esto mejora la experiencia de usuario. (

    Siempre Implementar
    )
  2. Modularización de Peticiones: Encapsula tu lógica de API en funciones o módulos separados para mantener tus componentes limpios y reutilizables. Por ejemplo:

// src/api/posts.js
import axios from 'axios';

const API_URL = 'https://jsonplaceholder.typicode.com';

export const getPosts = async () => {
const response = await axios.get(`${API_URL}/posts`);
return response.data;
};

export const createPost = async (postData) => {
const response = await axios.post(`${API_URL}/posts`, postData);
return response.data;
};
  1. Variables de Entorno: Nunca hardcodees URLs de API en tu código. Usa variables de entorno (process.env.REACT_APP_API_URL) para diferentes entornos (desarrollo, producción).

  2. Limpieza de Efectos (useEffect): Cuando hagas peticiones en useEffect, especialmente si hay posibilidades de que el componente se desmonte antes de que la petición termine, considera la cancelación de la petición para evitar errores de memoria o actualizaciones de estado en componentes no montados. Axios tiene una API para esto; con Fetch, es más manual con AbortController.

    Ejemplo con AbortController (Fetch API)
useEffect(() => {
const abortController = new AbortController();
const signal = abortController.signal;

fetch('https://jsonplaceholder.typicode.com/posts', { signal })
.then(response => response.json())
.then(data => setPosts(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Petición abortada');
} else {
setError(error);
}
});

return () => {
abortController.abort(); // Cancela la petición si el componente se desmonta
};
}, []);
</details>

5. Reintentos y Exponential Backoff: Para peticiones que pueden fallar temporalmente (problemas de red, límites de tasa), considera implementar una lógica de reintentos con exponential backoff. React Query lo maneja por defecto. Con Fetch o Axios, tendrías que implementarlo manualmente o usar una librería auxiliar.


Conclusión 🎯

Has llegado al final de esta guía completa sobre cómo consumir APIs REST en tus aplicaciones React. Hemos cubierto las herramientas fundamentales: la Fetch API nativa, la versátil librería Axios y el potente gestor de estado del servidor React Query.

La elección de la herramienta adecuada dependerá de las necesidades de tu proyecto. Para proyectos pequeños, Fetch puede ser suficiente. Para la mayoría de las aplicaciones con necesidades intermedias, Axios es una excelente opción. Y para aplicaciones grandes y complejas con mucha interacción de datos, React Query es la elección que te brindará el mayor beneficio en términos de rendimiento, experiencia de desarrollo y mantenimiento.

¡Experimenta con cada una y elige la que mejor se adapte a tus requerimientos para construir aplicaciones React eficientes y robustas! ¡Feliz codificación! 💻

Tutoriales relacionados

Comentarios (0)

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