Efectos
En React, algunos componentes tienen la necesidad de sincronizarse con sistemas externos. Por ejemplo, es posible que desees controlar un componente que no sea de React en función de un estado, configurar una conexión con un servidor, o consumir una API. Para esto se utilizan los efectos.
Los efectos te permiten ejecutar código después del renderizado para que puedas sincronizar tu componente con un sistema fuera de React.
Ciclo de vida de un componente
Antes de hablar en profundidad de los efectos, es necesario entender el ciclo de vida de un componente. En React, los componentes tienen un ciclo de vida que se divide en tres fases: montaje, actualización y desmontaje.
Montaje
El montaje ocurre cuando se crea una instancia de un componente y se inserta en el DOM.
Esta fase se inicializan los estados (si es que los tiene) y se enlazan los métodos.
Actualización
La actualización ocurre cuando se actualiza el estado de un componente o cuando se recibe una nueva prop. Como resultado de la actualización, se vuelve a renderizar el componente.
Este proceso es el cual se conoce como reconciliación. React compara el árbol de elementos resultante con el anterior y aplica los cambios necesarios al DOM, tal y como mencionamos en temas anteriores.
Sabemos por lo tanto que cada vez que se produce un evento, el cual provoca un cambio en el estado de un componente, este se vuelve a renderizar.
Desmontaje
El desmontaje ocurre cuando se elimina un componente del DOM. En esta fase se eliminan los enlaces a los métodos y se liberan los recursos que el componente estaba utilizando.
Por lo general, no prestamos mucha atención a esta fase, pero es importante tenerla en cuenta para evitar problemas de memoria.
Diferencia entre eventos y efectos
Comúnmente, cuando se habla de efectos, se suele confundir con los eventos. Sin embargo, debemos tener en cuenta que son dos conceptos diferentes.
Para comenzar, los eventos son acciones que se producen en un componente y que, por lo general, provocan un cambio en el estado del mismo. Tal y como vimos al definir manejadores de eventos.
Por otro lado, los efectos son acciones que se producen después del renderizado de un componente. Es decir, que se ejecutan después de que el componente se haya montado, actualizado o desmontado.
En otras palabras, los eventos dependen de la interacción del usuario con el componente, mientras que los efectos se ejecutan de forma independiente, ya que establecen relaciones con sistemas externos (servidores, APIs, etc.).
Por ejemplo, si tenemos un componente que muestra una lista de usuarios, y eliminamos uno de ellos, el componente se actualiza y se vuelve a renderizar. En este caso, podemos ver ambos conceptos en acción. El evento es el click sobre el botón de eliminar, y el efecto es la eliminación del usuario de la lista ya que debemos sincronizar el componente con el sistema externo que contiene la lista de usuarios.
Atención: no siempre necesitaremos efectos. Estos se usan típicamente para “salir” de React, estableciendo relaciones con sistemas independientes de nuestra aplicación. Si el efecto que queremos realizar se puede hacer empleando únicamente eventos y estados, no será necesario utilizar efectos.
Componentes puros
Por último, debemos tener en cuenta un concepto importante, el desarrollo de componentes puros. Estos están directamente relacionados con los efectos.
Un componente puro es aquel que no produce efectos secundarios sobre otros componentes. Es decir, que no modifica directamente el estado de otros componentes, variables, o cualquier otro elemento externo a ellos. Cada componente mantiene su propio estado y lo modifica únicamente a través de ciertos eventos que se disparan en el mismo, a través de sus propiedades y, a partir de ahora, a través de los efectos.
La idea de definir componentes puros es evitar errores difíciles de detectar. Si un componente modifica el estado de otros componentes, se vuelve complejo de mantener y seguir el flujo de datos de la aplicación. En el peor de los casos, povocaremos modificaciones en el estado de un componente que no esperábamos.
Por este motivo, es importante que cualquier cambio producido en un componente ocurra a través de sus propiedades, eventos o efectos, y únicamente durante las fases de montaje, actualización o desmontaje.
useEffect
Para crear un efecto en React, utilizamos el hook useEffect
. Como cualquier hook, sabemos que este es una función que debemos importar desde el módulo react
.
1import React, { useEffect } from 'react';
Por supuesto, sólo debemos definir este hook en componentes funcionales y en el nivel superior de los mismos (no dentro de bucles, condicionales, etc.).
El hook useEffect
recibe dos parámetros: una función y un array de dependencias, y no devuelve nada (es decir, es un hook que no retorna ningún valor).
La función que recibe como primer parámetro es la que se ejecutará como efecto cuando se produzca un cambio en las dependencias, las cuales no son más que estados o propiedades del componente.
El array de dependencias puede estar vacío, y se utiliza para indicar a React que el efecto debe ejecutarse únicamente cuando alguna de las dependencias cambie. Si no se especifica ninguna dependencia, el efecto se ejecutará cada vez que se produzca un cambio en el componente, es decir, en cada renderizado.
1useEffect(() => {2 // Efecto3}, [<dependencia1>, <dependencia2>]);
Como vemos en el ejemplo, estamos utilizando una función anónima como argumento del hook. Por lo general es la forma más común de utilizarlo, pero también podemos utilizar una función nombrada.
1useEffect(efecto, [<dependencia1>, <dependencia2>]);
En este caso, la función efecto
será la que se ejecute como efecto.
Para comprender mejor el funcionamiento de los efectos, vamos a ver un ejemplo. Supongamos que tenemos un componente que muestra un mensaje, en el cual tiene una cierta cantidad de likes.
1function Mensaje({ messageData }) {2 const [message, setMessage] = useState(null);3
4 useEffect(() => {5 // Buscar el mensaje por ID6 const foundMessage = messagesData.find((msg) => msg.id === messageId);7 if (foundMessage) {8 console.log(`Encontrado mensaje con ID ${messageId}`);9 setMessage(foundMessage);10 }11 }, [message]);12
13 const handleLike = () => {14 // Actualizar el atributo 'likes'15 setMessage((prevMessage) => ({16 ...prevMessage,17 likes: prevMessage.likes + 1,18 }));19 // Aquí iría el código para actualizar el archivo JSON, en un escenario real20 };21
22 if (!message) return <div>Cargando mensaje...</div>;23
24 return (25 <div className="message">26 <p>{message.text}</p>27 <button onClick={handleLike}>👍 Me gusta {message.likes}</button>28 </div>29 );30};31
32export default Message;
Este componente se contruirá en base a una API que nos devolverá el mensaje y la cantidad de likes. Para ello, utilizaremos el hook useState
para definir el estado del componente.
1function Mensaje({ messageData }) {2 const [message, setMessage] = useState(null);3
4 useEffect(() => {5 // Buscar el mensaje por ID6 const foundMessage = messagesData.find((msg) => msg.id === messageId);7 if (foundMessage) {8 console.log(`Encontrado mensaje con ID ${messageId}`);9 setMessage(foundMessage);10 }11 }, [message]);12
13 const handleLike = () => {14 // Actualizar el atributo 'likes'15 setMessage((prevMessage) => ({16 ...prevMessage,17 likes: prevMessage.likes + 1,18 }));19 // Aquí iría el código para actualizar el archivo JSON, en un escenario real20 };21
22 if (!message) return <div>Cargando mensaje...</div>;23
24 return (25 <div className="message">26 <p>{message.text}</p>27 <button onClick={handleLike}>👍 Me gusta {message.likes}</button>28 </div>29 );30};31
32export default Message;
La cantidad de likes será controlada por un estado, pero cada vez que se actualice el estado, debemos realizar una llamada a la API para actualizar el mensaje.
Como vemos, el estado message
es una dependencia del efecto, por lo que cada vez que se actualice, se ejecutará el efecto y se sincronizará el mensaje con la API.