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:

  1. Declara una función llamada handleClick dentro de tu componente Button.
  2. Implementa la lógica dentro de esa función (utiliza alert para mostrar el mensaje).
  3. 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.

Atención

Las funciones que se pasan a los manejadores de eventos deben ser pasadas, no llamadas. Por ejemplo:

pasar una función (correcto)llamar una función (incorrecto)
<button onClick={handleClick}><button onClick={handleClick()}>

La diferencia es sutil. En el primer ejemplo, la función handleClick es pasada como un manejador de evento onClick. Esto le dice a React que lo recuerde y solo llama la función cuando el usuario hace clic en el botón.

En el segundo ejemplo, los () al final del handleClick() ejecutan la funci√≥n inmediatamente mientras se renderiza, sin ning√ļn clic. Esto es porque el JavaScript dentro de { y } en JSX se ejecuta de inmediato.

Cuando escribes código en línea, la misma trampa se presenta de otra manera:

pasar una función (correcto)llamar una función (incorrecto)
<button onClick={() => alert('...')}><button onClick={alert('...')}>

Pasar código en línea así no lo ejecutará al hacer clic; lo ejecutará cada vez que el componente se renderice:

// Esta alerta se ejecuta cuando el componente se renderiza, ¬°no cuando se hace clic!
<button onClick={alert('¬°Me cliqueaste!')}>

Si quieres definir un manejador de evento en línea, envuélvelo en una función anónima de esta forma:

<button onClick={() => alert('¬°Me cliqueaste!')}>

En lugar de ejecutar el código que está dentro cada vez que se renderiza, esto crea una función para que se llame más tarde.

En ambos casos, lo que quieres pasar es una función:

  • <button onClick={handleClick}> pasa la funci√≥n handleClick.
  • <button onClick={() => alert('...')}> pasa la funci√≥n () => alert('...').

Lee m√°s sobre las funciones flecha.

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 pasa handlePlayClick como la prop onClick al Button que est√° dentro.
  • UploadButton pasa () => alert('Uploading!') como la prop onClick al Button 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 Buttons, 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á.

Atención

Todos los eventos se propagan en React excepto onScroll, el cual solo funciona en la etiqueta JSX a la que lo agregues.

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:

  1. React llama al manejador onClick pasado al <button>.
  2. 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 componente Toolbar.
  3. Esa función, definida en el componente Toolbar, muestra la alerta propia del botón.
  4. 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

Eventos de la fase de captura

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:

  1. Viaja hacia abajo, llamando a todos los manejadores onClickCapture.
  2. Ejecuta el manejador onClick del elemento pulsado.
  3. 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}, no onClick={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>
  );
}