Responder a eventos
React te permite añadir manejadores de eventos a tu JSX. Los manejadores de eventos son tus propias funciones que se ejecutarán en respuesta a interacciones como hacer clic, hover, enfocar inputs en formularios, entre otras.
Aprenderás
- Diferentes maneras de escribir un manejador de eventos
- Cómo pasar la lógica de manejo de eventos desde un componente padre
- Cómo los eventos se propagan y cómo detenerlos
Añadiendo manejadores de eventos
Para agregar un manejador de eventos, primero definirás una función y luego la pasarás como una prop a la etiqueta JSX apropiada. Por ejemplo, este es un botón que no hace nada todavía:
export default function Button() { return ( <button> No hago nada </button> ); }
Puedes hacer que muestre un mensaje cuando un usuario haga clic siguiendo estos tres pasos:
- Declara una función llamada
handleClick
dentro de tu componenteButton
. - Implementa la lógica dentro de esa función (utiliza
alert
para mostrar el mensaje). - Agrega
onClick={handleClick}
al JSX del<button>
.
export default function Button() { function handleClick() { alert('¡Me cliqueaste!'); } return ( <button onClick={handleClick}> Cliquéame </button> ); }
Definiste la función handleClick
y luego la pasaste como una prop al <button>
. handleClick
es un manejador de eventos. Las funciones manejadoras de eventos:
- Usualmente están definidas dentro de tus componentes.
- Tienen nombres que empiezan con
handle
, seguido del nombre del evento.
Por convención, es común llamar a los manejadores de eventos como handle
seguido del nombre del evento. A menudo verás onClick={handleClick}
, onMouseEnter={handleMouseEnter}
, etcétera.
Por otro lado, puedes definir un manejador de eventos en línea en el JSX:
<button onClick={function handleClick() {
alert('¡Me cliqueaste!');
}}>
O, de manera más corta, usando una función flecha:
<button onClick={() => {
alert('¡Me cliqueaste!');
}}>
Todos estos estilos son equivalentes. Los manejadores de eventos en línea son convenientes para funciones cortas.
Leyendo las props en manejadores de eventos
Como los manejadores de eventos son declarados dentro de un componente, tienen acceso a las props del componente. Este es un botón que, cuando recibe el clic, muestra una alerta con su prop message
:
function AlertButton({ message, children }) { return ( <button onClick={() => alert(message)}> {children} </button> ); } export default function Toolbar() { return ( <div> <AlertButton message="¡Reproduciendo!"> Reproducir película </AlertButton> <AlertButton message="¡Subiendo!"> Subir imagen </AlertButton> </div> ); }
Esto le permite a estos dos botones mostrar diferentes mensajes. Intenta cambiar los mensajes que se les pasan.
Pasar manejadores de eventos como props
A menudo querrás que el componente padre especifique un manejador de eventos de un componente hijo. Considera unos botones: dependiendo de dónde estás usando un componente Button
, es posible que quieras ejecutar una función diferente, tal vez una reproduzca una película y otra cargue una imagen.
Para hacer esto, pasa una prop que el componente recibe de su padre como el manejador eventos así:
function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } function PlayButton({ movieName }) { function handlePlayClick() { alert(`¡Reproduciendo ${movieName}!`); } return ( <Button onClick={handlePlayClick}> Reproducir "{movieName}" </Button> ); } function UploadButton() { return ( <Button onClick={() => alert('¡Subiendo!')}> Subir imagen </Button> ); } export default function Toolbar() { return ( <div> <PlayButton movieName="Kiki's Delivery Service" /> <UploadButton /> </div> ); }
Aquí, el componente Toolbar
renderiza un PlayButton
y un UploadButton
:
PlayButton
pasahandlePlayClick
como la proponClick
alButton
que está dentro.UploadButton
pasa() => alert('Uploading!')
como la proponClick
alButton
que está dentro.
Finalmente, tu componente Button
acepta una prop llamada onClick
. Pasa esa prop directamente al <button>
integrado en el navegador con onClick={onClick}
. Esto le dice a React que llame la función pasada cuando reciba un clic.
Si usas un sistema de diseño, es común para componentes como los botones que contengan estilos pero no especifiquen un comportamiento. En cambio, componentes como PlayButton
y UploadButton
pasarán los manejadores de eventos.
Nombrar props de manejadores de eventos
Componentes integrados como <button>
y <div>
solo admiten nombres de eventos del navegador como onClick
. Sin embargo, cuando estás creando tus propios componentes, puedes nombrar sus props de manejador de eventos como quieras.
Por convención, las props de manejadores de eventos deberían empezar con on
, seguido de una letra mayúscula.
Por ejemplo, la propiedad onClick
del componente Button
pudo haberse llamado onSmash
:
function Button({ onSmash, children }) { return ( <button onClick={onSmash}> {children} </button> ); } export default function App() { return ( <div> <Button onSmash={() => alert('¡Reproduciendo!')}> Reproducir película </Button> <Button onSmash={() => alert('¡Subiendo!')}> Subir imagen </Button> </div> ); }
En este ejemplo, <button onClick={onSmash}>
muestra que el <button>
(minúsculas) del navegador todavía necesita una prop llamada onClick
, ¡pero el nombre de la prop recibida por tu componente Button
personalizado depende de ti!
Cuando tu componente admite múltiples interacciones, podrías nombrar las props de manejadores de eventos para conceptos específicos de la aplicación. Por ejemplo, este componente Toolbar
recibe los manejadores de eventos de onPlayMovie
y onUploadImage
:
export default function App() { return ( <Toolbar onPlayMovie={() => alert('¡Reproduciendo!')} onUploadImage={() => alert('¡Subiendo!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Reproducir película </Button> <Button onClick={onUploadImage}> Subir imagen </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
Fíjate como el componente App
no necesita saber qué hará Toolbar
con onPlayMovie
o onUploadImage
. Eso es un detalle de implementación del Toolbar
. Aquí, Toolbar
los pasa como manejadores onClick
en sus Button
s, pero podría luego iniciarlos con un atajo de teclado. Nombrar props a partir de interacciones específicas de la aplicación como onPlayMovie
te da la flexibilidad de cambiar cómo se usan más tarde.
Propagación de eventos
Los manejadores de eventos también atraparán eventos de cualquier componente hijo que tu componente pueda tener. Decimos que un evento “se expande” o “se propaga” hacia arriba en el árbol de componentes cuando: empieza donde el evento sucedió, y luego sube en el árbol.
Este <div>
contiene dos botones. Tanto el <div>
como cada botón tienen sus propios manejadores onClick
. ¿Qué manejador crees que se activará cuando hagas clic en un botón?
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('¡Cliqueaste el Toolbar!'); }}> <button onClick={() => alert('¡Reproduciendo!')}> Reproducir película </button> <button onClick={() => alert('¡Subiendo!')}> Subir imagen </button> </div> ); }
Si haces clic en cualquiera de los botones, su onClick
se ejecutará primero, seguido por el onClick
del <div>
. Así que dos mensajes aparecerán. Si haces clic en el propio toolbar, solo el onClick
del <div>
padre se ejecutará.
Detener la propagación
Los manejadores de eventos reciben un objeto del evento como su único parámetro. Por convención, normalmente es llamado e
, que quiere decir “evento”. Puedes usar este objeto para leer información del evento.
Ese objeto del evento también te permite detener la propagación. Si quieres evitar que un evento llegue a los componentes padre, necesitas llamar e.stopPropagation()
como este componente Button
lo hace:
function Button({ onClick, children }) { return ( <button onClick={e => { e.stopPropagation(); onClick(); }}> {children} </button> ); } export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('¡Cliqueaste el Toolbar!'); }}> <Button onClick={() => alert('¡Reproduciendo!')}> Reproducir película </Button> <Button onClick={() => alert('¡Subiendo!')}> Subir imagen </Button> </div> ); }
Cuando haces clic en un botón:
- React llama al manejador
onClick
pasado al<button>
. - Ese manejador, definido en
Button
, hace lo siguiente:- Llama
e.stopPropagation()
, que evita que el evento se expanda aún más. - Llama a la función
onClick
, la cual es una prop pasada desde el componenteToolbar
.
- Llama
- Esa función, definida en el componente
Toolbar
, muestra la alerta propia del botón. - Como la propagación fue detenida, el manejador
onClick
del<div>
padre no se ejecuta.
Como resultado del e.stopPropagation()
, al hacer clic en los botones ahora solo muestra una alerta (la del <button>
) en lugar de las dos (la del <button>
y la del <div>
del toolbar padre). Hacer clic en un botón no es lo mismo que hacer clic en el toolbar que lo rodea, así que detener la propagación tiene sentido para esta interfaz.
Deep Dive
En raros casos, puede que necesites capturar todos los eventos en elementos hijos, incluso si pararon la propagación. Por ejemplo, tal vez quieras hacer log de cada clic para un análisis, independientemente de la lógica de propagación. Puedes hacer esto agregando Capture
al final del nombre del evento:
<div onClickCapture={() => { /* esto se ejecuta primero */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
Cada evento se propaga en tres fases:
- Viaja hacia abajo, llamando a todos los manejadores
onClickCapture
. - Ejecuta el manejador
onClick
del elemento pulsado. - Viaja hacia arriba, llamando a todos los manejadores
onClick
.
Los eventos de captura son útiles para código como enrutadores o para analítica, pero probablemente no lo usarás en código de aplicaciones.
Pasar manejadores como alternativa a la propagación
Fíjate como este manejador de clic ejecuta una línea de código y luego llama a la prop onClick
pasada por el padre:
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
También puede que añadas más código a este manejador antes de llamar al manejador de eventos onClick
del padre. Este patrón proporciona una alternativa a la propagación. Le permite al componente hijo manejar el evento, mientras también le permite al componente padre especificar algún comportamiento adicional. A diferencia de la propagación, no es automático. Pero el beneficio de este patrón es que puedes seguir claramente la cadena de código completa que se ejecuta como resultado de algún evento.
Si dependes de la propagación y es difícil rastrear cuales manejadores se ejecutaron y por qué, intenta este enfoque.
Evitar el comportamiento por defecto
Algunos eventos del navegador tienen comportamientos por defecto asociados a ellos. Por ejemplo, un evento submit de un <form>
, que ocurre cuando se pulsa un botón que está dentro de él, por defecto recargará la página completa:
export default function Signup() { return ( <form onSubmit={() => alert('¡Enviando!')}> <input /> <button>Send</button> </form> ); }
Puedes llamar e.preventDefault()
en el objeto del evento para evitar que esto suceda:
export default function Signup() { return ( <form onSubmit={e => { e.preventDefault(); alert('¡Enviando!'); }}> <input /> <button>Enviar</button> </form> ); }
No confundas e.stopPropagation()
y e.preventDefault()
. Ambos son útiles, pero no están relacionados:
e.stopPropagation()
evita que los manejadores de eventos adjuntos a etiquetas de nivel superior se activen.e.preventDefault()
evita el comportamiento por defecto del navegador para algunos eventos que lo tienen.
¿Pueden los manejadores de eventos tener efectos secundarios?
¡Absolutamente! Los manejadores de eventos son el mejor lugar para los efectos secundarios.
A diferencia de las funciones de renderizado, los manejadores de eventos no necesitan ser puros, asi que es un buen lugar para cambiar algo; por ejemplo, cambiar el valor de un input en respuesta a la escritura, o cambiar una lista en respuesta a un botón presionado. Sin embargo, para cambiar una información, primero necesitas alguna manera de almacenarla. En React, esto se hace usando el estado, la memoria de un componente. Aprenderás todo sobre ello en la siguiente página.
Recapitulación
- Puedes manejar eventos pasando una función como prop a un elemento como
<button>
. - Los manejadores de eventos deben ser pasados, ¡no llamados!
onClick={handleClick}
, noonClick={handleClick()}
. - Puedes definir una función manejadora de eventos de manera separada o en línea.
- Los manejadores de eventos son definidos dentro de un componente, así pueden acceder a las props.
- Puedes declarar un manejador de eventos en un padre y pasarlo como una prop al hijo.
- Puedes definir tus propias props manejadoras de eventos con nombres específicos de aplicación.
- Los eventos se propagan hacia arriba. Llama
e.stopPropagation()
en el primer parámetro para evitarlo. - Los eventos pueden tener comportamientos por defecto del navegador no deseados. Llama
e.preventDefault()
para prevenirlo. - Llamar explícitamente a una prop manejadora de eventos desde un manejador hijo es una buena alternativa a la propagación.
Desafío 1 de 2: Arregla un manejador de eventos
Al hacer clic en este botón se supone que debe cambiar el fondo de la página entre blanco y negro. Sin embargo, nada pasa cuando lo cliqueas. Soluciona el problema. (No te preocupes por la lógica dentro de handleClick
: esa parte está bien).
export default function LightSwitch() { function handleClick() { let bodyStyle = document.body.style; if (bodyStyle.backgroundColor === 'black') { bodyStyle.backgroundColor = 'white'; } else { bodyStyle.backgroundColor = 'black'; } } return ( <button onClick={handleClick()}> Alterna las luces </button> ); }