Skip to content
This page has been auto-translated and may contain errors.View in English

Efectos

Imagina un componente que necesita mantener un temporizador marcando después de renderizarse, como hace el componente Timer de abajo. La mayoría de lo que hace un componente sucede mientras se renderiza: lee props y estado y devuelve JSX. Mantener un temporizador en marcha es diferente. También lo es abrir una suscripción, sincronizar el título del documento, o traer datos desde un servidor. Cada uno de esos casos llega hacia afuera para tocar algo que React no controla. useEffect es el hook para eso. Te permite ejecutar código en un componente después de renderizarse, para que pueda sincronizarse con esa cosa del mundo exterior.

Aquí hay un efecto que inicia el temporizador una sola vez e incrementa un contador cada segundo:

jsx
import { useState, useEffect } from 'react'

function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const id = setInterval(() => setCount(c => c + 1), 1000)
    return () => clearInterval(id)
  }, [])

  return <p>{count}</p>
}

Tres partes están haciendo el trabajo aquí, y se corresponden con las tres cosas con las que todo efecto lidia: qué hacer, cómo limpiarlo, y cuándo ejecutarlo.

La función que pasas a useEffect es el efecto mismo. Esta llama a setInterval para iniciar un temporizador repetido que aumenta count en uno cada segundo.

El array de dependencias

El segundo argumento, el [] al final, es el array de dependencias. Controla cuándo se ejecuta el efecto.

  • Un array vacío [] significa que el efecto se ejecuta una vez, después de que el componente aparezca en la pantalla por primera vez. El temporizador de arriba usa esto: inicia el intervalo una sola vez y déjalo corriendo.
  • Un array con valores, como [a, b], significa que el efecto se ejecuta después del primer render y de nuevo cada vez que uno de esos valores cambia entre renders. Así es como resincronizas cuando algo de lo que depende el efecto ha cambiado.
  • Omitir el array por completo significa que el efecto se ejecuta después de cada render. Eso rara vez es lo que quieres, y es una fuente común de bucles descontrolados cuando el efecto también actualiza estado.

El array es tu respuesta a la pregunta "¿cuándo debería este código ejecutarse de nuevo?" Lista los valores que el efecto lee y de los que depende, y React lo ejecuta de nuevo cuando cualquiera de ellos cambia.

Funciones de limpieza

El efecto de arriba devuelve una función:

jsx
return () => clearInterval(id)

Esa función devuelta es la función de limpieza. React la ejecuta antes de que el efecto se ejecute de nuevo, y una vez más cuando el componente se quita de la pantalla. Su trabajo es deshacer lo que configuró el efecto.

La limpieza importa porque los efectos frecuentemente inician algo que sigue andando por su cuenta: un intervalo, un event listener, una conexión abierta. Si el componente se quita y nada detiene ese intervalo, sigue disparándose para siempre y mantiene una referencia a un componente que ya no existe. Hazlo algunas veces y tienes una fuga lenta con actualizaciones llegando a cosas que ya no existen. La regla de oro: si un efecto inicia algo, se suscribe a algo, o abre algo, su función de limpieza debe detenerlo, desuscribirse, o cerrarlo.

Es posible que no necesites un efecto

Los efectos son para sincronizar con el mundo exterior, así que mucho código que se ve como candidato para useEffect pertenece a otro lugar. Dos casos surgen constantemente.

El primero es valores derivados. Si puedes calcular algo de props y estado que ya tienes, calcúlalo justo durante el render:

jsx
function Cart({ items }) {
  const total = items.reduce((sum, item) => sum + item.price, 0)
  return <p>Total: {total}</p>
}

total se deriva de items, así que se calcula en cada render y siempre está sincronizado. Almacenarlo en su propio estado y actualizarlo desde un efecto sería más código y un render extra sin ganancia.

El segundo es responder a una acción del usuario. Cuando algo debe suceder porque el usuario hizo clic o escribió, ese código va en el manejador de eventos para esa acción:

jsx
function BuyButton({ product }) {
  function handleClick() {
    buyProduct(product)
  }
  return <button onClick={handleClick}>Buy</button>
}

La compra sucede en handleClick, justo donde se maneja el clic. Enrutarlo a través de un efecto añadiría una capa de indirección y haría el flujo más difícil de seguir. Una buena prueba: si el código se ejecuta por una interacción específica, usa un manejador de eventos; si se ejecuta para mantener el componente sincronizado con un sistema externo, usa un efecto.

La pregunta práctica con cualquier efecto es qué pertenece al array de dependencias. La respuesta es mecánica: todo valor reactivo que el efecto lee, es decir, todo prop, variable de estado, o valor derivado de ellos que aparezca dentro de la función del efecto, pertenece al array. Omite uno y el efecto seguirá usando una copia anticuada de él en lugar de captar cambios.

jsx
function SearchResults({ query }) {
  const [results, setResults] = useState([])

  useEffect(() => {
    let active = true
    fetch(`/api/search?q=${query}`)
      .then(r => r.json())
      .then(data => { if (active) setResults(data) })
    return () => { active = false }
  }, [query])

  return <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>
}

Este efecto lee query, así que query está en el array. Nada más en el scope cambia lo que el efecto hace, así que nada más necesita estar ahí. No tienes que resolverlo de memoria. La regla de lint exhaustive-deps de eslint-plugin-react-hooks, lee la función del efecto e identifica cualquier valor que usa que falta en el array. Trata sus advertencias como bugs reales que arreglar, no como ruido que silenciar, porque una advertencia suprimida aquí es exactamente cómo bugs de valores anticuados se filtran a producción.

Cuando un efecto comienza a hacer dos trabajos no relacionados, divídelo en dos efectos en lugar de un efecto con un array de dependencias más largo. Un componente que tanto trae datos de un usuario como establece el título del documento basado en ese usuario es más fácil de razonar como dos llamadas useEffect separadas, cada una con su propio array de dependencias enfocado, que como un efecto tratando ambas preocupaciones. Cada efecto debe sincronizar una cosa con el mundo exterior.

Una prueba rápida para saber si el código pertenece a un efecto en absoluto: ¿trae algo, se suscribe a algo, o llega y toca el DOM o algún otro sistema fuera de React directamente? Ese es territorio de efectos. ¿Calcula un valor de props o estado que ya tienes? Eso pertenece al cuerpo del render, no a un efecto. Recurrir a useEffect para derivar un valor es la forma más común en que los efectos se sobreusan.

El tiempo de los efectos merece precisión. Tu efecto no se ejecuta durante el render. React renderiza el componente, confirma la actualización del DOM, el navegador pinta, y solo entonces se ejecuta el efecto. Esto es deliberado: el efecto ve una pantalla que ya refleja el render actual, y nunca bloquea al navegador de pintar. También significa que no puedes confiar en un efecto para producir algo que el usuario vea antes del primer pintado. Para el caso más raro donde necesitas medir o mutar el DOM antes de pintar, existe useLayoutEffect, que se ejecuta sincronamente después de confirmar y antes de que el navegador pinte.

Notarás en desarrollo que los efectos se ejecutan dos veces en mount. El Strict Mode de React deliberadamente monta cada componente, lo desmonta, y lo monta de nuevo. Ejecuta tu efecto, ejecuta la limpieza, luego ejecuta el efecto una segunda vez. Esto es una prueba de estrés para la limpieza. Si tu efecto configura algo pero nunca lo deshace, la ejecución doble expone el bug de inmediato, porque verás dos intervalos o dos suscripciones en lugar de uno. Esto solo sucede en desarrollo. En producción el efecto se ejecuta una vez.

Dos trampas del array de dependencias merecen internalizarse. La primera son closures anticuados. Un efecto captura los valores del render en el que fue creado. Si omites un valor del array de dependencias, el efecto sigue leyendo el valor antiguo de ese render incluso después de que ha cambiado, y nunca se ejecuta de nuevo para captar el nuevo. La solución es listar todo valor que el efecto lee, o usar la forma updater como setCount(c => c + 1) para que no necesites leer el valor actual en absoluto. La segunda es identidad. Los objetos, arrays, y funciones creados durante el render son valores frescos cada vez, así que listar uno como dependencia hace que el efecto se ejecute en cada render, ya que la referencia nunca es igual a la de la última vez. Cuando necesitas uno como dependencia, defínelo dentro del efecto, o envuélvelo en useMemo o useCallback (hooks que cachean un valor o función entre renders) para que su identidad se mantenga estable entre renders.

Nota de versión

En class components este comportamiento se dividía entre tres métodos del ciclo de vida: componentDidMount para configuración después del primer render, componentDidUpdate para ejecutarse de nuevo cuando los datos cambiaban, y componentWillUnmount para limpieza. Un único useEffect con un array de dependencias y una función de limpieza cubre los tres, por eso el código relacionado de configuración y desmontaje ahora vive junto en lugar de esparcirse entre métodos separados.

JunoLos efectos sincronizan con el mundo exterior Piensa en useEffect como el lugar para código que llega fuera de React: un temporizador, el título del documento, una solicitud a un servidor. El array al final dice cuándo ejecutarlo, [] significando una vez, justo después de que el componente aparezca. Y si tu efecto inicia algo, devuelve una pequeña función que lo detenga. La mayoría del tiempo no necesitarás un efecto en absoluto, así que es un buen hábito pausar y preguntar si un cálculo simple o un manejador de clic haría el trabajo primero.
JunoLos efectos sincronizan con el mundo exterioruseEffect se ejecuta después del render para sincronizar con algo que React no controla. El array de dependencias es el control: [] se ejecuta una vez, [a, b] se ejecuta de nuevo cuando esos cambian, y sin array se ejecuta en cada render. Devuelve una función de limpieza para cualquier cosa que inicies, a la que te suscribas, o que abras. Antes de escribir uno, verifica si el valor puede derivarse durante el render o el trabajo pertenece a un manejador de eventos, porque esos cubren una sorprendente cantidad de lo que se ve como territorio de efectos.
JunoLos efectos sincronizan con el mundo exterior Los efectos se ejecutan después de confirmar, así que ven una pantalla pintada y nunca la bloquean; recurre a useLayoutEffect solo cuando debes tocar el DOM antes de pintar. El doble mount de Strict Mode en desarrollo es una prueba de limpieza, así que trata cualquier intervalo o suscripción duplicado como un bug real. Las dos trampas de dependencias son closures anticuados por valores omitidos e identidades inestables de objetos o funciones que ejecutan el efecto en cada render; la forma updater, useMemo, y useCallback son cómo las desactivas.

Siguiente: Traer datos, donde los componentes traen datos que viven completamente fuera de React.