En React, los efectos secundarios se gestionan mediante el hook useEffect
, pero antes de empezar a trabajar con ellos necesitamos entender sus bases. En programación, llamamos efecto secundario (o side-effect) a cualquier acción que realiza nuestro código (en el interior de una función, por ejemplo) y afecta a algo que se encuentra fuera del ámbito de esa función, es decir, que estamos alterando algo no relacionado directamente con lo que devolvemos en la función.
Efectos secundarios
Para entender mejor el concepto de efecto secundario de la programación, imaginemos esta sencilla función de un contador:
let counter = 0;
function incrementCounter() {
counter++; // Modifica la variable global (efecto secundario)
}
incrementCounter();
console.log(counter); // 1
Dicha función está alterando el valor de counter
, el cuál no pertenece a la función incrementCounter()
, por lo que aunque está realizando nuestro objetivo correctamente (incrementar el contador), se está realizando mediante un efecto secundario.
Algunos ejemplos comunes de efectos secundarios en JavaScript podrían ser:
- Modificar variables fuera del ámbito de su función (ej: variables globales)
- Realizar una operación de entrada/salida (ej: escribir en un archivo, petición a una API...)
- Actualizar y modificar elementos del DOM (ej: cambios en la estructura de la página)
- Crear temporizadores (ej: setTimeout o setInterval)
- Suscribirse a eventos (ej: escuchar un click u otra acción)
Funciones puras
El ejemplo anterior se puede reeescribir intentando que tus funciones sean puras. Una función pura es una función que cumple estas condiciones:
- 1️⃣ Dado un argumento por parámetro, siempre devuelve el mismo resultado (es determinista).
- 2️⃣ No muta nada fuera de su ámbito (no tiene efectos secundarios).
Veamos el ejemplo anterior, cambiando la función incrementCounter()
para que sea una función pura, y evitando la creación de efectos secundarios:
let counter = 0;
function incrementCounter(counter) {
return counter + 1;
}
counter = incrementCounter(counter);
console.log(counter); // 1
Observa que en lugar de mutar la variable externa counter
en la función, lo que hacemos es devolver una versión modificada de los datos que recibimos. Esto ayuda considerablemente a no crear efectos secundarios y a que nuestro código sea más predecible y evitemos bugs accidentales.
Side effects en React
En el ecosistema de React, el concepto efecto secundario (muchas veces abreviado como efecto) aparece mucho y también se suele relacionar con las acciones que tienen consecuencias fuera de la función o componente en la que se ejecuta.
En ReactLand™ se tiende a evitar los efectos secundarios en las funciones o componentes siempre que sea posible, ya que no encajan bien con la filosofía de React:
- 1️⃣ React renderiza la UI cuando cambia el estado (predecible). Los efectos no son predecibles.
- 2️⃣ Efectos innecesarios pueden impactar negativamente en el rendimiento.
- 3️⃣ Los efectos secundarios descontrolados son dificiles de depurar y testear.
Sin embargo, a pesar de estas dificultades, los efectos secundarios son a menudo inevitables para construir una aplicación. Para ello, existe el hook useEffect
, una herramienta para manejarlos.
El hook useEffect
En React, existe un hook llamado useEffect
que sirve para gestionar efectos secundarios de forma controlada, predecible y eficiente. Primero, analicemos la estructura de un useEffect()
para conocer las partes clave de este hook:
useEffect(() => {
/* Montaje */
return () => { /* Desmontaje */ }
}, [/* dependencias */]);
- 1️⃣ El
useEffect()
tiene dos parámetros: la función (de montaje) y las dependencias. - 2️⃣ La función del
useEffect
se ejecuta cuando se monta el componente - 3️⃣ El array
deps
indica las dependencias deluseEffect
- 4️⃣ La función devuelta por el
return
se ejecuta cuando se desmonta el componente
La parte más importante aquí probablemente sea el array de dependencias, así que vamos a explicarlo primero. Las situaciones son las siguientes:
Dependencias | Descripción | Montaje | En cada renderizado |
---|---|---|---|
[] | Sólo se ejecuta la primera vez (montaje). | ✅ | ❌ |
[dep1] | Se ejecuta primera vez y al cambiar. | ✅ | 🟨 Cuando cambia dep1 |
[dep1, dep2] | Se ejecuta primera vez y al cambiar. | ✅ | 🟨 Cuando cambian dep1 y/o dep2 |
Sin array de dependencias | Se ejecuta primera vez y en cada renderizado. | ✅ | ✅ Siempre |
Con esto claro, veamos ahora un código real utilizando un useEffect
. Vamos a crear un temporizador que se ejecutará cada segundo para mostrar la hora actual.
Al inicio del componente Clock
creamos un estado time
con la hora actual. Luego, creamos un useEffect
con un array de dependencias vacío, por lo que se ejecutará solo la primera vez que se monta el componente:
import { useState, useEffect } from "react";
export function Clock() {
const [time, setTime] = useState(new Date().toLocaleTimeString());
useEffect(() => {
const interval = setInterval(() => {
setTime(new Date().toLocaleTimeString());
}, 1000);
return () => clearInterval(interval);
}, []);
return <h1>Hora actual: {time}</h1>;
}
En dicho useEffect
creamos el setInterval
cada 1 segundo (1000ms) y mutamos el estado time
para cambiar la hora actual.
Por último, en el return
devolvemos una función que se ejecutará cuando se desmonte el componente, donde es necesario realizar tareas de limpieza para evitar fugas de memoria o problemas similares.
De esta forma puedes gestionar los efectos secundarios en tus componentes de React de forma controlada. Aunque es un sistema muy potente, ten mucho cuidado y no abuses de
useEffect
ya que puede complicar la lógica, impactar en el rendimiento y muchas veces se puede buscar una alternativa sin efectos secundarios.