45. useEffect - ¿Qué es y para qué se usa?

 

Tutorial: useEffect en React - ¿Qué es y para qué se usa?

🎯 Introducción

Seguramente alguna vez hayas tenido que realizar operaciones como:

  • Recuperación de datos desde una API

  • Suscripciones a servicios externos

  • Modificación manual del DOM desde componentes

Estas operaciones se llaman efectos secundarios porque pueden afectar a otros componentes y no pueden realizarse durante el renderizado. Se deben ejecutar cuando el renderizado ya ha finalizado.

🔍 ¿Qué es useEffect?

useEffect es un Hook de React que nos permite realizar efectos secundarios desde componentes funcionales. Reemplaza los métodos del ciclo de vida de los componentes de clase:

  • componentDidMount

  • componentDidUpdate

  • componentWillUnmount

Todo esto se unifica en un solo Hook: useEffect.





📝 Sintaxis Básica

javascript

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


function Example() {

    const [count, setCount] = useState(0);


    // useEffect se ejecuta después de cada renderizado

    useEffect(() => {

        // Efecto secundario aquí

        document.title = `You clicked ${count} times`;

    });


    return (

        <div>

            <p>You clicked {count} times</p>

            <button onClick={() => setCount(count + 1)}>

                Click me

            </button>

        </div>

    );

}

🔄 ¿Cuándo se ejecuta useEffect?

  1. Después del renderizado inicial (equivalente a componentDidMount)

  2. Después de cada actualización (equivalente a componentDidUpdate)

  3. Antes de desmontar el componente (con función de limpieza, equivalente a componentWillUnmount)




🎨 Ejercicio Práctico

Vamos a analizar el código de la imagen adjunta:

javascript

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


function Example() {

    const [count, setCount] = useState(0);


    // Similar a componentDidMount y componentDidUpdate

    useEffect(() => {

        // Actualiza el título del documento usando la API del navegador

        document.title = 'You clicked ${count} times'; // NOTA: Hay un error aquí

    });


    return (

        <div>

            <p>You clicked {count} times</p>

            <button onClick={() => setCount(count + 1)}>

                Click me

            </button>

        </div>

    );

}

⚠️ Corrección necesaria:

En el código hay un error común: se usan comillas simples en lugar de backticks para la template string:

Incorrecto: 'You clicked ${count} times'
Correcto: `You clicked ${count} times`

📊 ¿Qué hace este código?

  1. Estado: Crea un estado count inicializado en 0

  2. Efecto: Cada vez que count cambia (después del renderizado), actualiza el título de la página

  3. Interfaz: Muestra el contador y un botón para incrementarlo

🎯 Resumen

Ciclo de Vida (Clases)

Hook (Funcional)

componentDidMount()

useEffect(() => {...}, [])

componentDidUpdate()

useEffect(() => {...})

componentWillUnmount()

useEffect(() => { return () => {...} })

💡 Puntos Clave

  1. useEffect se ejecuta después del renderizado

  2. Puede depender de variables específicas (con el array de dependencias)

  3. Puede devolver una función de limpieza

  4. Reemplaza múltiples métodos del ciclo de vida en uno solo

🚀 Próximos Pasos

En el siguiente tutorial veremos:

  • Array de dependencias ([], [variable], sin array)

  • Funciones de limpieza

  • useEffect para llamadas a APIs

  • Buenas prácticas y errores comunes

❓ ¿Dudas?

Si tienes preguntas sobre useEffect, recuerda que:

  • Se ejecuta después del renderizado

  • Puede controlar efectos secundarios

  • Es más simple que los métodos de ciclo de vida de clases

¡Practica con el ejemplo y nos vemos en el siguiente tutorial!


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

jsx

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 APIs1. 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

jsx

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)

jsx

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

// 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

18-Ciclo de Vida de un Componente React

20. ¿Que son los Props de React?

17-Componentes en React-estado