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:
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:
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:
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:
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.
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.
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.
Siguiente: Obtener datos, donde los componentes obtienen datos que viven completamente fuera de React.

