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 lo hace el componente Timer de abajo. La mayoría de lo que hace un componente ocurre durante el renderizado: lee props y estado y devuelve JSX. Mantener un temporizador en marcha es diferente. Lo mismo ocurre con abrir una suscripción, sincronizar el título del documento o obtener datos de un servidor. Cada uno de estos alcanza el mundo externo para tocar algo que React no maneja. useEffect es el hook para eso. Te permite ejecutar código después de que el componente se renderice, para que pueda sincronizarse con ese elemento externo.

Aquí hay un efecto que inicia el temporizador una sola vez y marca 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 tiene que lidiar: qué hacer, cómo limpiarlo y cuándo ejecutarlo.

La función que pasas a useEffect es el efecto en sí. Este llama a setInterval para iniciar un temporizador repetido que incrementa 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 sola vez, después de que el componente aparece en la pantalla por primera vez. El temporizador de arriba usa esto: inicia el intervalo una vez y déjalo ejecutándose.
  • Un array con valores, como [a, b], significa que el efecto se ejecuta después del primer renderizado y nuevamente cada vez que uno de esos valores cambia entre renderizados. Así es como te resincronizas cuando algo de lo que el efecto depende ha cambiado.
  • Omitir el array completamente significa que el efecto se ejecuta después de cada renderizado. Eso es raramente lo que quieres, y es una fuente común de bucles descontrolados cuando el efecto también actualiza estado.

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

Funciones de limpieza

El efecto anterior 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 elimina de la pantalla. Su tarea es deshacer lo que el efecto configuró.

La limpieza es importante porque los efectos a menudo inician algo que continúa por su cuenta: un intervalo, un detector de eventos, una conexión abierta. Si se elimina el componente y nada detiene ese intervalo, sigue disparándose eternamente y mantiene una referencia a un componente que ya no existe. Haz eso algunas veces y tienes una fuga lenta y 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.

Podría ser que no necesites un efecto

Los efectos son para sincronizarse con el mundo externo, así que mucho código que parece un candidato para useEffect pertenece a otro lugar. Dos casos ocurren constantemente.

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

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 renderizado y siempre está sincronizado. Almacenarlo en su propio estado y actualizarlo desde un efecto sería más código y una renderización extra sin ganancia alguna.

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

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

La compra ocurre 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 más difícil seguir el flujo. Una buena prueba: si el código se ejecuta por una interacción específica, usa un controlador 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é debe estar en el array de dependencias. La respuesta es mecánica: todo valor reactivo que el efecto lee, es decir, toda prop, variable de estado o valor derivado de ellos que aparezca dentro de la función de efecto, debe estar en el array. Omite uno y el efecto seguirá usando una copia antigua de ese valor en lugar de recoger los 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 hace el efecto, así que nada más necesita estar ahí. No tienes que acordarte de esto de memoria. La regla de lint exhaustive-deps de eslint-plugin-react-hooks lee la función de efecto y marca cualquier valor que use y que falte en el array. Trata sus advertencias como bugs reales para arreglar, no como ruido que silenciar, porque una advertencia suprimida aquí es exactamente cómo los bugs de valor obsoleto se cuelan a producción.

Cuando un efecto empieza 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 obtiene datos de un usuario como establece el título del documento basándose en ese usuario es más fácil de entender como dos llamadas useEffect separadas, cada una con su propio array de dependencias enfocado, que como un efecto jugglando ambas preocupaciones. Cada efecto debe sincronizar una cosa con el mundo externo.

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

El timing de los efectos vale la pena ser preciso. Tu efecto no se ejecuta durante el renderizado. 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 renderizado 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 de la primera pintura. Para el caso más raro donde necesites medir o mutar el DOM antes de pintar, está 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 intencionalmente monta cada componente, lo desmonta y lo monta de nuevo. Ejecuta tu efecto, ejecuta la limpieza, luego ejecuta el efecto una segunda vez. Esta es una prueba de estrés para la limpieza. Si tu efecto configura algo pero nunca lo desactiva, la ejecución doble expone el bug inmediatamente, 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 valen la pena internalizarlas. La primera es cierres obsoletos. Un efecto captura los valores del renderizado en el que fue creado. Si omites un valor del array de dependencias, el efecto sigue leyendo el valor antiguo de ese renderizado incluso después de que ha cambiado, y nunca se vuelve a ejecutar para recoger 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 renderizado son valores frescos cada vez, así que listar uno como una dependencia hace que el efecto se vuelva a ejecutar en cada renderizado, 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 renderizados) para que su identidad se mantenga estable entre renderizados.

Nota de versión

En class components este comportamiento estaba dividido en tres métodos del ciclo de vida: componentDidMount para la configuración después del primer renderizado, componentDidUpdate para volver a ejecutar cuando los datos cambiaban, y componentWillUnmount para la limpieza. Un único useEffect con un array de dependencias y una función de limpieza cubre los tres, por eso el código de configuración y desactivación relacionado ahora vive junto en lugar de estar disperso en métodos separados.

JunoLos efectos sincronizan con el mundo externo Piensa en useEffect como el lugar para el código que alcanza fuera de React: un temporizador, el título del documento, una solicitud a un servidor.

El array al final dice cuándo ejecutarlo, [] significa una sola vez, justo después de que el componente aparezca. Y si tu efecto inicia algo, devuelve una pequeña función que lo detiene.

La mayoría de las veces no necesitarás un efecto en absoluto, así que es una buena costumbre pausar y preguntarte si un cálculo simple o un controlador de clic harían el trabajo primero.

JunoLos efectos sincronizan con el mundo externouseEffect se ejecuta después de renderizar para sincronizarse con algo que React no posee.

El array de dependencias es el control: [] se ejecuta una vez, [a, b] se vuelve a ejecutar cuando esos cambian, y sin array se ejecuta cada renderizado. Devuelve una función de limpieza para cualquier cosa que inicies, te suscribas o abras.

Antes de escribir uno, verifica si el valor se puede derivar durante el renderizado o si el trabajo pertenece a un controlador de eventos, porque esos cubren una cantidad sorprendente de lo que parece territorio de efecto.

JunoLos efectos sincronizan con el mundo externo Los efectos se ejecutan después de confirmar, así que ven una pantalla pintada y nunca la bloquean; recurre a useLayoutEffect solo cuando debas tocar el DOM antes de pintar.

El double 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 dependencia son cierres obsoletos por valores omitidos e identidades de objeto o función inestables que re-ejecutan el efecto cada renderizado; la forma updater, useMemo y useCallback son cómo las desactivas.

Siguiente: Obtener datos, donde los componentes obtienen datos que viven completamente fuera de React.