13.1-useEffect para Llamadas a APIs: Guía Práctica

 useEffect para Llamadas a APIs: Guía Práctica

useEffect es el hook de React que te permite sincronizar tu componente con sistemas externos, como APIs, bases de datos, o cualquier operación que ocurre "fuera" de React.

La idea principal

Cuando necesitas traer datos de una API cuando el componente se monta, o cuando cierta variable cambia, usas useEffect.

Estructura básica:

jsx

useEffect(() => {

  // Código que se ejecuta (efecto)

  

  return () => {

    // Código de limpieza (opcional)

  };

}, [dependencias]); // Array de dependencias

Ejemplo más simple: Traer datos al montar

import { useState, useEffect } from 'react';


function Usuario() {

  const [usuario, setUsuario] = useState(null);

  const [cargando, setCargando] = useState(true);

  const [error, setError] = useState(null);


  useEffect(() => {

    // Esta función se ejecuta cuando el componente se monta

    const fetchUsuario = async () => {

      try {

        setCargando(true);

        const response = await fetch('https://api.ejemplo.com/usuario/1');

        const data = await response.json();

        setUsuario(data);

      } catch (err) {

        setError('Error al cargar usuario');

      } finally {

        setCargando(false);

      }

    };


    fetchUsuario();

  }, []); // Array vacío = se ejecuta solo al montar


  if (cargando) return <p>Cargando...</p>;

  if (error) return <p>{error}</p>;

  

  return (

    <div>

      <h1>{usuario.nombre}</h1>

      <p>Email: {usuario.email}</p>

    </div>

  );

}

Patrones comunes para APIs-1. Búsqueda con parámetros

function BuscadorProductos() {

  const [productos, setProductos] = useState([]);

  const [busqueda, setBusqueda] = useState('');

  const [cargando, setCargando] = useState(false);

  // Se ejecuta cuando cambia 'busqueda'

  useEffect(() => {

    // Evitar búsqueda si está vacío

    if (!busqueda.trim()) {

      setProductos([]);

      return;

    }

    const buscarProductos = async () => {

      setCargando(true);

      try {

        const response = await fetch(

          `https://api.ejemplo.com/productos?q=${busqueda}`

        );

        const data = await response.json();

        setProductos(data);

      } catch (error) {

        console.error('Error:', error);

      } finally {

        setCargando(false);

      }

    };

    // Debounce: espera 300ms antes de hacer la petición

    const timer = setTimeout(buscarProductos, 300); 

    // Cleanup: cancela la petición si el usuario sigue escribiendo

    return () => clearTimeout(timer);

  }, [busqueda]); // Dependencia: se ejecuta cuando 'busqueda' cambia

  return (

    <div>

      <input

        type="text"

        value={busqueda}

        onChange={(e) => setBusqueda(e.target.value)}

        placeholder="Buscar productos..."

      />

      {cargando && <p>Buscando...</p>}

      <ul>

        {productos.map((producto) => (

          <li key={producto.id}>{producto.nombre}</li>   ))}

      </ul>    </div> );}

2. Datos que dependen de otros datos

function PerfilUsuario({ userId }) {

  const [usuario, setUsuario] = useState(null);

  const [posts, setPosts] = useState([]);


  // Traer usuario cuando cambie userId

  useEffect(() => {

    const fetchUsuario = async () => {

      const response = await fetch(

        `https://jsonplaceholder.typicode.com/users/${userId}`

      );

      const data = await response.json();

      setUsuario(data);

    };

    

    fetchUsuario();

  }, [userId]); // Se ejecuta cuando userId cambia


  // Traer posts del usuario cuando usuario esté disponible

  useEffect(() => {

    if (!usuario) return; // No ejecutar si no hay usuario

    

    const fetchPosts = async () => {

      const response = await fetch(

        `https://jsonplaceholder.typicode.com/posts?userId=${userId}`

      );

      const data = await response.json();

      setPosts(data);

    };

    

    fetchPosts();

  }, [usuario, userId]); // Depende de usuario y userId


  // Renderizar...

}

3. POST a una API (crear recursos)

function FormularioNuevoPost() {

  const [titulo, setTitulo] = useState('');

  const [cuerpo, setCuerpo] = useState('');

  const [enviando, setEnviando] = useState(false);

  const [mensaje, setMensaje] = useState('');


  const handleSubmit = async (e) => {

    e.preventDefault();

    setEnviando(true);

    

    try {

      const response = await fetch('https://jsonplaceholder.typicode.com/posts', {

        method: 'POST',

        headers: {

          'Content-Type': 'application/json',

        },

        body: JSON.stringify({

          title: titulo,

          body: cuerpo,

          userId: 1,

        }),

      });

       const data = await response.json();

      setMensaje('¡Post creado con éxito!');

      setTitulo('');

      setCuerpo('');

      console.log('Respuesta:', data);

    } catch (error) {

      setMensaje('Error al crear post');

    } finally {

      setEnviando(false);

    }

  };

  // Limpiar mensaje después de 3 segundos

  useEffect(() => {

    if (mensaje) {

      const timer = setTimeout(() => {

        setMensaje('');

      }, 3000);

      

      return () => clearTimeout(timer);

    }  }, [mensaje]);


  return (

    <form onSubmit={handleSubmit}>

      <input

        value={titulo}

        onChange={(e) => setTitulo(e.target.value)}

        placeholder="Título"

      />

      <textarea

        value={cuerpo}

        onChange={(e) => setCuerpo(e.target.value)}

        placeholder="Contenido"

      />

      <button type="submit" disabled={enviando}>

        {enviando ? 'Enviando...' : 'Crear Post'}

      </button>

      {mensaje && <p>{mensaje}</p>}

    </form>

  );

}

Manejo de errores robusto

jsx

function ApiConManejoErrores() {

  const [datos, setDatos] = useState(null);

  const [estado, setEstado] = useState('idle'); // 'idle', 'loading', 'success', 'error'


  useEffect(() => {

    const fetchData = async () => {

      setEstado('loading');

      

      try {

        // Simular error aleatorio

        if (Math.random() > 0.7) {

          throw new Error('Error de conexión simulado');

        }

        

        const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');

        

        if (!response.ok) {

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

        }

        

        const data = await response.json();

        setDatos(data);

        setEstado('success');

      } catch (error) {

        setEstado('error');

        console.error('Error en fetch:', error);

      }

    };


    fetchData();

  }, []);


  // Renderizar según estado

  switch (estado) {

    case 'idle':

      return null;

    case 'loading':

      return <div>Cargando datos...</div>;

    case 'error':

      return <div>Error al cargar datos. Intenta de nuevo.</div>;

    case 'success':

      return (

        <div>

          <h2>{datos.title}</h2>

          <p>ID: {datos.id}</p>

          <p>Completado: {datos.completed ? 'Sí' : 'No'}</p>

        </div>

      );

    default:

      return null;

  }

}

Buenas prácticas para APIs

1. Abortar peticiones al desmontar

jsx

useEffect(() => {

  const controller = new AbortController();

  const signal = controller.signal;


  const fetchData = async () => {

    try {

      const response = await fetch('https://api.ejemplo.com/datos', { signal });

      // Procesar respuesta

    } catch (error) {

      if (error.name === 'AbortError') {

        console.log('Fetch abortado');

      } else {

        // Manejar otros errores

      }

    }

  };


  fetchData();


  // Cleanup: aborta la petición si el componente se desmonta

  return () => controller.abort();

}, []);

2. Custom Hook reutilizable

jsx

// hooks/useApi.js

function useApi(url) {

  const [data, setData] = useState(null);

  const [loading, setLoading] = useState(true);

  const [error, setError] = useState(null);


  useEffect(() => {

    const fetchData = async () => {

      try {

        setLoading(true);

        const response = await fetch(url);

        if (!response.ok) throw new Error('Error en la petición');

        

        const result = await response.json();

        setData(result);

      } catch (err) {

        setError(err.message);

      } finally {

        setLoading(false);

      }

    };


    fetchData();

  }, [url]);


  return { data, loading, error };

}


// Uso en componente

function MiComponente() {

  const { data, loading, error } = useApi('https://jsonplaceholder.typicode.com/users');

  

  if (loading) return <p>Cargando...</p>;

  if (error) return <p>Error: {error}</p>;

  

  return (

    <ul>

      {data?.map(user => (

        <li key={user.id}>{user.name}</li>

      ))}

    </ul>

  );

}

Reglas de oro para useEffect con APIs

  1. Siempre maneja estados de carga y error

  2. Usa cleanup para abortar peticiones pendientes

  3. No olvides las dependencias en el array

  4. Evita llamadas innecesarias con condiciones

  5. Separa responsabilidades en múltiples useEffect si es necesario

  6. Considera usar una librería como React Query o SWR para casos complejos

Resumen visual

text

useEffect(() => {

  // 1. Iniciar carga

  // 2. Hacer fetch a la API

  // 3. Procesar respuesta

  // 4. Actualizar estado

  

  return () => {

    // Limpiar (abortar fetch, limpiar timeouts)

  };

}, [dependencias]); // ¿Cuándo se ejecuta?

¡Comienza con ejemplos simples y ve incrementando la complejidad! La práctica es clave para dominar useEffect con APIs


Comentarios

Entradas más populares de este blog

9.3-Tutorial de Componentes React con Props -SSS

9.6-Ciclo de Vida de un Componente React

9-Componentes en React-estado