El propósito de este artículo es explicar cómo funciona la comunicación entre componentes. Comprender este mecanismo, es crucial para dominar el comportamiento de los componentes y alcanzar los objetivos deseados en el desarrollo de aplicaciones.
Me propongo abordar este tema de manera sencilla y accesible, asegurando que la explicación sea fácil de seguir. Para facilitar la comprensión, incluiré ejemplos prácticos y esquemas ilustrativos.
Cuando trabajamos con componentes, es fundamental entender cómo pueden comunicarse entre sí. Para empezar, explicaré cómo se comunican sin utilizar estados; más adelante, los añadiré a la ecuación.
Principalmente, existen dos formas de establecer esta comunicación: de un componente padre a su hijo y viceversa. Para ilustrar mas fácilmente este concepto, consideremos un ejemplo:
En el ejemplo, disponemos de un componente padre llamado
<Home>
y un componente hijo llamado<CustomSelector>
, que muestra una lista de ítems que pueden estar en estadoselected
ounselected
.
En este caso, el componente <CustomSelector>
recibe, a través de una prop enviada desde <Home>
, la lista de ítems seleccionados. <CustomSelector>
se encargará de representar esta lista, mostrando únicamente los ítems seleccionados.
<CustomSelector
items={items},
selectedItems={selectedItems}
/>
Ejemplo: Todos los items unselected
.
// pages/index.tsx
import { CustomSelector, Item } from '../components/CustomSelector'
const Home = () => {
const items: Item[] = [
{ text: "Item 1" },
{ text: "Item 2" },
{ text: "Item 3" }
]
const selectedItems: number[] = []
return (
<CustomSelector
items={items},
selectedItems={selectedItems}
/>
)
}
export default Home
Funcionamiento:
Resultado:
En el segundo caso, es <CustomSelector>
quien transmite la lista de ítems seleccionados a <Home>
.
Al hacer clic en alguno de los ítems de <CustomSelector>
, se dispara el evento onClick.
onClick={() => handleClick(index)}
El evento onClick ejecuta la función handleClick, la cual se encarga de crear una nueva lista de ítems seleccionados.
const handleClick = (index: number) => {
const newSelectedItems = selectedItems.includes(index)
? selectedItems.filter((item) => item !== index)
: [...selectedItems, index]
// more code
}
Luego, esta lista actualizada se envía al componente <Home>
mediante el evento onChange.
onChange(newSelectedItems)
Funcionamiento:
Código completo:
// components/CustomSelector.tsx
interface Item = {
text: string
}
interface Props = {
items: Item[]
selectedItems: number[]
onChange: (selectedItems: number[]) => void
}
export const CustomSelector: React.FC<Props> = ({
items,
selectedItems,
onChange
}) => {
const handleClick = (index: number) => {
const newSelectedItems = selectedItems.includes(index)
? selectedItems.filter((item) => item !== index)
: [...selectedItems, index];
onChange(newSelectedItems)
}
return (
<div>
{items.map((item, index) => (
<div
key={index}
className={`${selectedItems.includes(index)
? "border-2 border-yellow-600"
: "border-2 border-gray-300"
}`}
onClick={() => handleClick(index)}
>
{item.text}
</div>
))}
</div>
)
}
Ha llegado el momento de utilizar los estados, y quiero asegurarme de que no queden dudas sobre lo que son y cómo funcionan. A continuación, dejo una breve explicación aclaratoria.
Ya hemos visto cómo se comportan los componentes al comunicarse entre sí. Una vez entendido esto, viene lo interesante: añadir los estados para persistir los ítems seleccionados, cerrar el círculo y completar el comportamiento deseado en el ejemplo.
Continuando con el ejemplo anterior, presentaré dos escenarios en los que se pueden aplicar los estados.
Aquí <CustomSelector>
maneja su propio estado, actualizándolo según sea necesario.
const [selectedItems, setSelectedItems] = useState<number[]>([])
Al igual que en los ejemplos anteriores, <CustomSelector>
dispone de un evento onClick que se dispara cuando se hace clic en un ítem y esto ejecuta la función handleClick. La diferencia en este caso es que <CustomSelector>
no cuenta con un onChange, ya que no necesita comunicarse con ningún componente padre, de lo que sí se encarga es de actualizar la lista de ítems seleccionados en el estado selectedItems
que el mismo contiene.
const handleClick = (index: number) => {
const newSelectedItems = selectedItems.includes(index)
? selectedItems.filter((item) => item !== index)
: [...selectedItems, index]
setSelectedItems(newSelectedItems)
}
Y es aquí donde ocurre la magia. Al actualizar el estado con setSelectedItems(newSelectedItems)
, el componente se vuelve a renderizar, mostrando los ítems seleccionados actualizados.
Funcionamiento:
Resultado:
Código completo:
// pages/index.tsx
import { CustomSelector, Item } from '../components/CustomSelector'
const Home = () => {
const items: Item[] = [
{ text: "Item 1" },
{ text: "Item 2" },
{ text: "Item 3" }
]
return (
<CustomSelector
items={items}
/>
)
}
export default Home
// components/CustomSelector.tsx
import React, { useState } from 'react'
interface Item {
text: string
}
interface Props {
items: Item[]
}
export const CustomSelector: React.FC<Props> = ({
items,
}) => {
const [selectedItems, setSelectedItems] = useState<number[]>([])
const handleClick = (index: number) => {
const newSelectedItems = selectedItems.includes(index)
? selectedItems.filter((item) => item !== index)
: [...selectedItems, index];
setSelectedItems(newSelectedItems)
}
return (
<div>
{items.map((item, index) => (
<div
key={index}
className={`${selectedItems.includes(index)
? "border-2 border-yellow-600"
: "border-2 border-gray-300"
}`}
onClick={() => handleClick(index)}
>
{item.text}
</div>
))}
</div>
)
}
Este ejemplo es altamente versátil, ya que desde <Home>
podemos incluso relacionar el comportamiento de distintos componentes entre sí utilizando los estados, aprovechando el re-renderizado y apoyándonos en el onChange de cada uno de estos componentes, aunque no abordaré este aspecto en el artículo.
Continuaré con el ejemplo anterior, aunque en este caso el componente <Home>
es quien tiene el estado. Vamos a poder ver la comunicación reciproca entre los dos componentes, <Home>
y <CustomSelector>
. Esto permitirá que ambos componentes se comuniquen entre sí y se sincronicen mutuamente, manteniendo el estado que contiene <Home>
actualizado en todo momento. El flujo de los datos entre ambos componentes es el siguiente:
Flujo de Datos con onChange
Cuando se utiliza el evento onChange para comunicar componentes, es esencial comprender cómo se propaga el flujo de datos, y qué ocurre en cada paso del ciclo de vida de la actualización.
Disparar el evento onClick:
Cuando un usuario interactúa con un ítem en <CustomSelector>
, se dispara el evento onClick. Esto ejecuta una función handleClick.
// components/CustomSelector.tsx
onClick={() => handleClick(index)}
Ejecución de handleClick:
Dentro de la función handleClick, determinamos si un ítem debe añadirse o eliminarse de la lista de seleccionados. Una vez que la nueva lista está preparada, ésta se pasa al componente <Home>
a través del evento onChange. Todo esto ya lo hemos visto anteriormente, pero falta explicar como utilizar ese onChange, para que <Home>
reciba la lista de items seleccionados y actualice el estado de <Home>
, vamos a ello.
// components/CustomSelector.tsx
const handleClick = (index: number) => {
const newSelectedItems = selectedItems.includes(index)
? selectedItems.filter((item) => item !== index)
: [...selectedItems, index];
onChange(newSelectedItems)
}
Recepción en Home:
En <Home>
, la función handleChange se ejecuta con la lista de ítems seleccionados como parámetro que llega desde <CustomSelector>
por el onChange. Aquí es donde se decide cómo actualizar el estado, en este caso, actualizando selectedItems
en <Home>
.
// pages/index.tsx
const handleChange = (currentSelectedItems: number[]) => {
setSelectedItems(currentSelectedItems)
}
Actualización del Estado:
Al actualizar el estado selectedItems
en <Home>
, se provoca un re-renderizado de <Home>
, lo que a su vez actualiza <CustomSelector>
, ya que el estado actualizado se pasa de nuevo como una prop, haciendo que <CustomSelector>
también se re-renderice con la lista de items actualizados.
Hemos cerrado el círculo de comunicación entre los dos componentes. Ahora, tanto si seleccionamos un ítem en <CustomSelector>
como si actualizamos el estado en <Home>
, ambos componentes se sincronizarán correctamente, mostrando siempre los ítems seleccionados actualizados.
Funcionamiento:
Código completo:
// pages/index.tsx
import React, { useState } from 'react'
import { CustomSelector, Item } from '../components/CustomSelector'
const Home = () => {
const items: Item[] = [
{ text: "Item 1" },
{ text: "Item 2" },
{ text: "Item 3" }
]
const [selectedItems, setSelectedItems] = useState<number[]>([])
const handleChange = (currentSelectedItems: number[]) => {
setSelectedItems(currentSelectedItems)
}
return (
<CustomSelector
items={items}
selectedItems={selectedItems}
onChange={handleChange}
/>
)
}
export default Home
// components/CustomSelector.tsx
import React from 'react'
interface Item {
text: string
}
interface Props {
items: Item[]
selectedItems: number[]
onChange: (selectedItems: number[]) => void
}
export const CustomSelector: React.FC<Props> = ({
items,
selectedItems,
onChange,
}) => {
const handleClick = (index: number) => {
const newSelectedItems = selectedItems.includes(index)
? selectedItems.filter((item) => item !== index)
: [...selectedItems, index];
onChange(newSelectedItems)
}
return (
<div>
{items.map((item, index) => (
<div
key={index}
className={`${selectedItems.includes(index)
? "border-2 border-yellow-600"
: "border-2 border-gray-300"
}`}
onClick={() => handleClick(index)}
>
{item.text}
</div>
))}
</div>
)
}
Errores comunes
Al trabajar con el evento onChange y la sincronización de estados entre componentes, pueden surgir algunos errores o comportamientos inesperados como re-renderizados innecesarios, desincronización del estado, o bucle infinito de actualizaciones de estado. Por ejemplo, para evitar los re-renderizados innecesarios cuando se actualiza el estado, se puede incluir un condicional.
// pages/index.tsx
const handleChange = (currentSelectedItems: number[]) => {
if (currentSelectedItems !== selectedItems) {
setSelectedItems(currentSelectedItems)
}
}
Para aquellas interesadas en profundizar más sobre la sincronización y asincronía de los estados en React, recomiendo leer el artículo de Jorge Aguiar Martín y Adrián Ferrera González titulado “Modificación concurrente de estado en React”. Este artículo ofrece una visión más detallada y avanzada, y es un excelente complemento para entender los desafíos y soluciones en la gestión de estados en situaciones más complejas.
He intentado plasmar de la manera más sencilla posible, cómo se comunican los componentes y la versatilidad que aportan los estados. Combinar ambos conceptos nos permitirá crear aplicaciones muy potentes, donde el único límite será tu imaginación al utilizarlos.
Quiero dar un agradecimiento muy especial a Michael, el compañero que tuvo la paciencia infinita para explicarme y enseñarme todo lo que sé sobre la comunicación entre componentes. Sin sus lecciones y su buena onda, este artículo no habría sido posible.
También quiero agradecer de corazón a Lita, que me dió un feedback sincero y de primera para mejorar este artículo. Gracias a sus observaciones, pude ver otro punto de vista y darle una vuelta más a lo que había escrito.
¡Gracias, Michael y Lita, por su apoyo y por hacer este camino mucho más divertido!
Las “props” (abreviatura de propiedades), son un mecanismo para pasar datos desde un componente padre a un componente hijo en una aplicación React. Permiten que los componentes sean reutilizables y dinámicos, ya que los datos que reciben pueden cambiar según el contexto en el que se utilicen. Además de pasar simples valores como cadenas o números, las props también pueden ser funciones, objetos complejos, o incluso otros componentes. Esto permite un alto grado de flexibilidad y personalización en la construcción de interfaces.
Componente padre
El componente Home pasa la prop name con el valor “John” al componente Greeting.
// pages/index.tsx
import Greeting from '../components/Greeting'
const Home = () => {
return (
<Greeting name="John" />
)
}
export default Home
Componente hijo
El componente Greeting recibe la prop name y la utiliza para mostrar un saludo personalizado.
// components/Greeting.tsx
const Greeting = ({ name }) => {
return <p>Hello, {name}!</p>
}
export default Greeting
Resultado
`Hello, Jonh!`
Los estados son un mecanismo para manejar datos que pueden cambiar a lo largo del ciclo de vida de un componente. A diferencia de las props, que son valores estáticos pasados desde un componente padre, los estados son variables internas de un componente que se pueden actualizar y provocar un nuevo renderizado del componente cuando cambian.
Por ejemplo, los estados son útiles para manejar la interacción del usuario, como entradas en formularios, cambios en la interfaz de usuario, o para almacenar datos que se obtienen de una API.
Los estados se gestionan normalmente utilizando el hook useState, que proporciona un valor inicial y una función para actualizar ese valor.
Ejemplo:
import React, { useState } from 'react'
const Counter = () => {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
<p>Current count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
)
}
export default Counter
En el ejemplo, el estado count se incrementa cada vez que el usuario hace clic en el botón, y el componente se re-renderiza para mostrar el nuevo valor.
Funcionamiento
Definimos un estado llamado “count” con un valor inicial de 0, creamos la función handleClick para manejar el incremento del contador y al hacer clic en el botón, se llama a la función handleClick que actualiza el valor del estado actual sumando 1.
Resultado
La actualización del estado hace que el componente se vuelva a renderizar y muestre el contador actualizado.
Los eventos son acciones o sucesos, como clics del usuario, que pueden ser detectados por el navegador para desencadenar una respuesta en una aplicación. Los eventos suelen manejarse mediante funciones específicas (event handlers) que responden a interacciones del usuario.
Ejemplo:
<button onClick={handleClick}>Click me</button>
En el ejemplo, onClick es el evento, se dispara cada vez que el usuario hace clic en el botón y se ejecuta la función handleClick.
Renderizar es el proceso de generar y mostrar el contenido de una página web, que puede hacerse en el servidor (SSR), durante la compilación (SSG), o en el cliente (CSR), dependiendo de cómo y cuándo se necesita el contenido.
¿Quieres más? te invitamos a suscribirte a nuestro boletín para avisarte cada vez que recopilemos contenido de calidad que compartir.
Si disfrutas leyendo nuestro blog, ¿imaginas lo divertido que sería trabajar con nosotros? ¿te gustaría?
Pero espera 🖐 que tenemos un conflicto interno. A nosotros las newsletter nos parecen 💩👎👹 Por eso hemos creado la LEAN LISTA, la primera lista zen, disfrutona y que suena a rock y reggaeton del sector de la programación. Todos hemos recibido newsletters por encima de nuestras posibilidades 😅 por eso este es el compromiso de la Lean Lista