Responder a eventos
React te permite añadir controladores de eventos a tu JSX. Los controladores 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 controlador de evento
- Cómo pasar la lógica de control de eventos desde un componente padre
- Cómo los eventos se propagan y cómo detenerlos
Añadiendo controladores de eventos
Para agregar un controlador de evento, 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 hiciste clic!'); } return ( <button onClick={handleClick}> Hazme clic </button> ); }
Definiste la función handleClick
y luego la pasaste como una prop al <button>
. handleClick
es un controlador de evento. Las funciones controladoras de evento:
- 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 controladores 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 controlador de evento en línea en el JSX:
<button onClick={function handleClick() {
alert('¡Me hiciste clic!');
}}>
O, de manera más corta, usando una función flecha:
<button onClick={() => {
alert('¡Me hiciste clic!');
}}>
Todos estos estilos son equivalentes. Los controladores de eventos en línea son convenientes para funciones cortas.
Leyendo las props en controladores de eventos
Como los controladores de eventos son declarados dentro de un componente, tienen acceso a las props del componente. Este es un botón que, cuando se hace 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 controladores de eventos como props
A menudo querrás que el componente padre especifique un controlador de evento 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 controlador de evento 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: Entregas a domicilio" /> <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 controladores de eventos.
Nombrar props de controladores 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 controlador de evento como quieras.
Por convención, las props de controladores 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 controladores de eventos para conceptos específicos de la aplicación. Por ejemplo, este componente Toolbar
recibe los controladores 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 controladores 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 controladores 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 controladores onClick
. ¿Qué controlador crees que se activará cuando hagas clic en un botón?
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('¡Hiciste clic en la barra de herramientas!'); }}> <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 controladores 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('¡Hiciste clic en la barra de herramientas!'); }}> <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 controlador
onClick
pasado al<button>
. - Ese controlador, 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 controlador
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.
Profundizar
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 controladores
onClickCapture
. - Ejecuta el controlador
onClick
del elemento en que se hace clic. - Viaja hacia arriba, llamando a todos los controladores
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 controladores como alternativa a la propagación
Fíjate como este controlador 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 controlador antes de llamar al controlador de evento onClick
del padre. Este patrón proporciona una alternativa a la propagación. Le permite al componente hijo controlar 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 controladores 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 hace clic en 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>Enviar</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 controladores 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 controladores de eventos tener efectos secundarios?
¡Absolutamente! Los controladores de eventos son el mejor lugar para los efectos secundarios.
A diferencia de las funciones de renderizado, los controladores 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 controlar eventos pasando una función como prop a un elemento como
<button>
. - Los controladores de eventos deben ser pasados, ¡no llamados!
onClick={handleClick}
, noonClick={handleClick()}
. - Puedes definir una función controladora de evento de manera separada o en línea.
- Los controladores de eventos son definidos dentro de un componente, así pueden acceder a las props.
- Puedes declarar un controlador de evento en un padre y pasarlo como una prop al hijo.
- Puedes definir tus propias props controladoras de evento 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 controladora de evento desde un controlador hijo es una buena alternativa a la propagación.
Desafío 1 de 2: Arregla un controlador de evento
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> ); }