Inicio / Artículos / Deje de recurrir por defecto a useEffect para cada problema en React.

Deje de recurrir por defecto a useEffect para cada problema en React.

Los efectos se sincronizan con sistemas externos; se generan durante el renderizado, manejan los clics en los eventos y obtienen datos reales con herramientas especializadas.

1424 palabras

La trampa de useEffect

Los aprendices de hooks suelen recurrir a useEffect para realizar búsquedas, obtener estado derivado o manejar clics en botones. Los efectos se sincronizan con sistemas externos; no son la herramienta por defecto para la lógica de renderizado habitual.

Qué hace realmente useEffect

Después de renderizar, React ejecuta los efectos para agregar oyentes, establecer conexiones o comunicarse de forma imperativa con algo fuera de React. Su uso inadecuado provoca parpadeos, búsquedas duplicadas y errores de competencia.

// ❌ BAD: Two renders, unnecessary state
function UserProfile({ firstName, lastName }) {
  const [fullName, setFullName] = useState('');
useEffect(() => {
    setFullName(`${firstName} ${lastName}`);
  }, [firstName, lastName]);
  return <h1>{fullName}</h1>;
}
// ✅ GOOD: Calculate during render
function UserProfile({ firstName, lastName }) {
  const fullName = `${firstName} ${lastName}`;
return <h1>{fullName}</h1>;
}
// ❌ BAD: Unnecessary effect + state
function ProductList({ products }) {
  const [sortedProducts, setSortedProducts] = useState([]);
useEffect(() => {
    const sorted = [...products].sort((a, b) => a.price - b.price);
    setSortedProducts(sorted);
  }, [products]);
  return (
    <ul>
      {sortedProducts.map(product => (
        <li key={product.id}>{product.name} - ${product.price}</li>
      ))}
    </ul>
  );
}
// ✅ GOOD: Calculate during render (useMemo for expensive ops)
function ProductList({ products }) {
  const sortedProducts = useMemo(() => {
    return [...products].sort((a, b) => a.price - b.price);
  }, [products]);
return (
    <ul>
      {sortedProducts.map(product => (
        <li key={product.id}>{product.name} - ${product.price}</li>
      ))}
    </ul>
  );
}
// ❌ BAD: Manual fetching with useEffect
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
useEffect(() => {
    let cancelled = false;
    async function fetchUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error('Failed to fetch');
        const data = await response.json();
        if (!cancelled) setUser(data);
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    }
    fetchUser();
    return () => { cancelled = true; };
  }, [userId]);
  if (loading) return <Spinner />;
  if (error) return <p>Error: {error}</p>;
  return <div>{user.name}</div>;
}
// ✅ GOOD: Use TanStack Query (React Query)
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
  const { data: user, isPending, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
  });
  if (isPending) return <Spinner />;
  if (error) return <p>Error loading user</p>;
  return <div>{user.name}</div>;
}
// ❌ BAD: Overcomplicating with useEffect
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
useEffect(() => {
    if (liked) {
      fetch(`/api/posts/${postId}/like`, { method: 'POST' });
    } else {
      fetch(`/api/posts/${postId}/unlike`, { method: 'POST' });
    }
  }, [liked, postId]);
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}
// ✅ GOOD: Handle in the event handler
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
const handleLike = async () => {
    const newLiked = !liked;
    setLiked(newLiked);
    await fetch(`/api/posts/${postId}/${newLiked ? 'like' : 'unlike'}`, {
      method: 'POST',
    });
  };
  return (
    <button onClick={handleLike}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}
function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);

    // Cleanup is crucial!
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  return <p>Window width: {width}px</p>;
}
function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);
useEffect(() => {
    const connection = createWebSocketConnection(roomId);

    connection.onMessage = (msg) => {
      setMessages(prev => [...prev, msg]);
    };
    connection.connect();
    return () => {
      connection.disconnect(); // Cleanup!
    };
  }, [roomId]);
  return <MessageList messages={messages} />;
}
function ChartComponent({ data }) {
  const chartRef = useRef(null);
  const chartInstance = useRef(null);
useEffect(() => {
    if (chartRef.current && !chartInstance.current) {
      chartInstance.current = new ChartLibrary(chartRef.current, {
        type: 'line',
        data: data,
      });
    }
    return () => {
      chartInstance.current?.destroy();
      chartInstance.current = null;
    };
  }, []);
  return <canvas ref={chartRef} />;
}
function PageTitle({ title }) {
  useEffect(() => {
    document.title = `${title} | My App`;

    return () => {
      document.title = 'My App'; // Reset on unmount
    };
  }, [title]);
return null;
}

Cuatro errores comunes

1. Calcular valores en los efectos

Derivar durante el renderizado (o useMemo cuando sea costoso). Los efectos que modifican setState a partir de props/estado suelen contener bucles de retroalimentación prohibidos.

2. Cálculos costosos en los efectos

Mantenga los cálculos puros en el renderizado/memo, no después de la pintura.

3. Obtención de datos como un efecto sin control

La obtención de datos requiere cancelación, caché y una buena experiencia de usuario ante errores. Prefiera bibliotecas o frameworks especializados con mecanismos de carga; si utiliza efectos, gestione las interrupciones y los conflictos.

4. Acciones del usuario en los efectos

Los manejadores de clics deben estar en los eventos. Los efectos que se activan al cambiar un indicador debido a un clic añaden indirección y errores.

Árbol de decisiones

Si calcula a partir del estado/props existente → renderizado. Si responde a un clic/envío → manejador de eventos. Si interactúa con una API del DOM, una fuente de red o el ciclo de vida de un widget de terceros → efecto.

Cuándo son adecuados los efectos

Escuchadores de eventos externos

WebSockets

Inicialización de bibliotecas de terceros

APIs imperativas no basadas en React que deben liberar recursos de forma limpia

Cierre

Menos efectos, flujo de datos más claro. Reserva useEffect para la sincronización con sistemas externos a React, no como herramienta por defecto para cada acción posterior al renderizado.

El Modo Estricto ejecuta los efectos dos veces durante el desarrollo para detectar tareas de limpieza faltantes; considera eso una ventaja al conectar sockets. Prefiere bibliotecas de datos declarativas para el estado del servidor, de modo que los componentes describan sus dependencias en lugar de crear efectos de fetch manualmente. Mide antes de agregar efectos que solo copien propiedades al estado; ese patrón suele ser un malentendido sobre el proceso de renderizado.

Las funciones de limpieza deben eliminar los escuchadores y temporizadores; los manejadores que permanecen activos son una causa común de advertencias sobre setState-on-unmounted.

Los arrays de dependencias forman parte del contrato: si se omiten valores, aparecen cierres obsoletos; si se enumeran demasiados objetos y efectos, cada renderizado se vuelve lento.

Al sincronizar con parámetros de búsqueda en la URL o almacenamientos externos, es preferible utilizar ganchos diseñados específicamente para ello que encapsulen los límites de los efectos.

Las aplicaciones renderizadas en el servidor no deben iniciar efectos exclusivos del navegador sin medidas de protección; se debe controlar esto según el entorno o el ciclo de vida.

Las funciones de limpieza deben eliminar los listeners y temporizadores; los manejadores que permanecen activos son una causa común de advertencias sobre setState-on-unmounted.

Los arrays de dependencias forman parte del contrato: si se omiten valores, aparecen cierres obsoletos; si se enumeran demasiados objetos y efectos, cada renderizado se vuelve lento.

Al sincronizar con parámetros de búsqueda en la URL o almacenamientos externos, es preferible utilizar ganchos diseñados específicamente para ello que encapsulen los límites de los efectos.

Las aplicaciones renderizadas en el servidor no deben iniciar efectos exclusivos del navegador sin medidas de protección; se debe controlar esto según el entorno o el ciclo de vida.

Las funciones de limpieza deben eliminar los listeners y temporizadores; los manejadores que permanecen activos son una causa común de advertencias relacionadas con setState-on-unmounted.

Las matrices de dependencias forman parte del contrato establecido: si se omiten valores, aparecen cierres obsoletos; incluir demasiados objetos y efectos ralentiza cada renderizado.

Al sincronizar con parámetros de búsqueda de URL o almacenamientos externos, es preferible utilizar hooks diseñados específicamente para encapsular los límites de los efectos.

Las aplicaciones renderizadas en el servidor no deben iniciar efectos exclusivos del navegador sin medidas de protección; se debe controlar esto según el entorno o el ciclo de vida.

Las funciones de limpieza deben eliminar los listeners y temporizadores; los manejadores que permanecen activos son una causa común de advertencias relacionadas con setState-on-unmounted.

Los arrays de dependencias forman parte del contrato: si se omiten valores, aparecen cierres obsoletos; si se enumeran demasiados objetos y efectos, cada renderizado se vuelve lento.

Al sincronizar con parámetros de búsqueda en la URL o almacenamientos externos, es preferible utilizar ganchos diseñados específicamente para ello que encapsulen los límites de los efectos.

Las aplicaciones renderizadas en el servidor no deben iniciar efectos exclusivos del navegador sin medidas de protección; se debe controlar esto según el entorno o el ciclo de vida.

Las funciones de limpieza deben eliminar los listeners y temporizadores; los manejadores que permanecen activos son una causa común de advertencias sobre setState-on-unmounted.

Los arrays de dependencias forman parte del contrato: si se omiten valores, aparecen cierres obsoletos; si se enumeran demasiados objetos y efectos, cada renderizado se vuelve lento.

Al sincronizar con parámetros de búsqueda en la URL o almacenamientos externos, es preferible utilizar ganchos diseñados específicamente para ello que encapsulen los límites de los efectos.

Las aplicaciones renderizadas en el servidor no deben iniciar efectos exclusivos del navegador sin medidas de protección; se debe controlar esto según el entorno o el ciclo de vida.

Las funciones de limpieza deben eliminar los listeners y temporizadores; los manejadores que permanecen activos son una causa común de advertencias relacionadas con setState-on-unmounted.

Las arrays de dependencias forman parte del contrato establecido: si se omiten valores, aparecerán cierres obsoletos; incluir demasiados objetos y efectos ralentiza cada renderizado.

Al sincronizar con parámetros de búsqueda de URL o almacenamientos externos, es preferible utilizar hooks diseñados específicamente para encapsular los límites de los efectos.

Las aplicaciones renderizadas en el servidor no deben iniciar efectos exclusivos del navegador sin medidas de protección; se debe controlar esto según el entorno o el ciclo de vida.

Las funciones de limpieza deben eliminar los listeners y temporizadores; los manejadores que permanecen activos son una causa común de advertencias relacionadas con setState-on-unmounted.

Los arrays de dependencias forman parte del contrato: si se omiten valores, aparecen cierres obsoletos; si se enumeran demasiados objetos, cada renderizado se vuelve lento.

Al sincronizar con parámetros de búsqueda de URL o almacenamientos externos, es preferible utilizar ganchos diseñados específicamente para ello que encapsulen los límites del efecto.

Las aplicaciones renderizadas en el servidor no deben iniciar efectos exclusivos para el navegador sin medidas de protección; se debe controlar esto según el entorno o el ciclo de vida.

Las funciones de limpieza deben eliminar los listeners y temporizadores; los manejadores que permanecen activos son una causa común de advertencias sobre setState-on-unmounted.

Lecturas relacionadas

  • Un kit de gancho personalizables reutilizables para cada proyecto nuevo de React — Explore un conjunto seleccionado de gancho personalizados de React que abarcan almacenamiento, atenuación de eventos, clics y obtención de datos, y que eliminan el código genérico repetitivo en los proyectos nuevos.