Tema
Eventos
Los eventos permiten que tu interfaz reaccione a lo que hace el usuario: clics, teclas, envío de formularios, mover el mouse. Aprenderás a manejarlos en React, a usar el objeto evento y a comunicar un componente hijo con su padre.
Concepto
En React los eventos se escriben en camelCase y reciben una función (no un string):
jsx
// HTML
<button onclick="saludar()">Saludar</button>
// React
<button onClick={saludar}>Saludar</button>Analogía: le das al botón el número de teléfono de quien debe atender la llamada (la función), no haces la llamada en ese momento.
Pasar la función, no ejecutarla
jsx
function App() {
function saludar() {
alert('¡Hola!')
}
return (
<>
{/* ✅ Pasa la referencia: se ejecuta al hacer clic */}
<button onClick={saludar}>Bien</button>
{/* ❌ La ejecuta al renderizar (y onClick recibe undefined) */}
<button onClick={saludar()}>Mal</button>
{/* ✅ Para pasar argumentos, envuelve en una función flecha */}
<button onClick={() => alert('Hola, Ana')}>Con argumento</button>
</>
)
}Eventos más comunes
| Evento | Cuándo ocurre | Elementos |
|---|---|---|
onClick | Clic | Cualquiera (sobre todo button) |
onChange | Cada cambio de valor | input, textarea, select |
onSubmit | Envío de formulario | form |
onKeyDown / onKeyUp | Tecla presionada / soltada | Elementos con foco |
onFocus / onBlur | Recibe / pierde el foco | Inputs |
onMouseEnter / onMouseLeave | Mouse entra / sale | Cualquiera |
onScroll | Desplazamiento | Elementos con scroll |
onDoubleClick | Doble clic | Cualquiera |
El objeto evento
React pasa un objeto evento (un SyntheticEvent que funciona igual en todos los navegadores):
jsx
function Ejemplos() {
function manejarClic(e) {
console.log(e.type) // "click"
console.log(e.target) // el elemento donde se hizo clic
console.log(e.clientX, e.clientY)
}
function manejarTecla(e) {
if (e.key === 'Enter') console.log('Enviar:', e.target.value)
if (e.key === 'Escape') e.target.value = ''
if (e.ctrlKey && e.key === 's') {
e.preventDefault() // evita "Guardar página" del navegador
console.log('Guardado')
}
}
return (
<>
<button onClick={manejarClic}>Clic</button>
<input onKeyDown={manejarTecla} placeholder="Presiona Enter o Escape" />
</>
)
}preventDefault y stopPropagation
jsx
function Formulario() {
function enviar(e) {
e.preventDefault() // evita que el formulario recargue la página
console.log('Enviado sin recargar')
}
return (
<form onSubmit={enviar}>
<input name="email" />
<button type="submit">Enviar</button>
</form>
)
}
function Tarjeta() {
return (
<div onClick={() => console.log('Abrir tarjeta')} className="tarjeta">
<h3>Producto</h3>
<button
onClick={(e) => {
e.stopPropagation() // evita que el clic "suba" y abra la tarjeta
console.log('Agregar al carrito')
}}
>
Agregar
</button>
</div>
)
}Los eventos burbujean: un clic en el botón también llega a sus padres. stopPropagation corta esa cadena.
Eventos + estado
jsx
import { useState } from 'react'
function ContadorCaracteres() {
const [texto, setTexto] = useState('')
const LIMITE = 140
return (
<div>
<textarea
value={texto}
onChange={(e) => setTexto(e.target.value.slice(0, LIMITE))}
rows={4}
cols={40}
/>
<p style={{ color: texto.length > 120 ? 'crimson' : 'gray' }}>
{texto.length}/{LIMITE}
</p>
</div>
)
}Comunicación hijo → padre
Los datos bajan con props. Para que un hijo avise al padre, el padre le pasa una función por props y el hijo la llama. Por convención se nombran onAlgo (prop) y manejarAlgo o handleAlgo (función).
jsx
// Componente reutilizable: no sabe qué hará el padre con el valor
export default function Calificacion({ valor, onCambiar }) {
return (
<div role="radiogroup" aria-label="Calificación">
{[1, 2, 3, 4, 5].map((n) => (
<button
key={n}
type="button"
onClick={() => onCambiar(n)}
aria-label={`${n} estrellas`}
style={{ fontSize: 28, background: 'none', border: 'none', cursor: 'pointer' }}
>
{n <= valor ? '★' : '☆'}
</button>
))}
</div>
)
}jsx
import { useState } from 'react'
import Calificacion from './components/Calificacion'
export default function App() {
const [estrellas, setEstrellas] = useState(0)
const mensajes = ['', 'Muy malo', 'Malo', 'Regular', 'Bueno', '¡Excelente!']
return (
<main style={{ textAlign: 'center', marginTop: 40 }}>
<h2>¿Qué te pareció el producto?</h2>
{/* El padre guarda el estado; el hijo sólo avisa */}
<Calificacion valor={estrellas} onCambiar={setEstrellas} />
<p>{mensajes[estrellas]}</p>
</main>
)
}Levantar el estado
Si dos componentes hermanos necesitan el mismo dato, el estado se mueve ("se levanta") al padre común, que lo pasa a ambos:
jsx
function App() {
const [busqueda, setBusqueda] = useState('')
return (
<>
<BarraBusqueda valor={busqueda} onCambiar={setBusqueda} />
<ListaResultados filtro={busqueda} />
</>
)
}
function BarraBusqueda({ valor, onCambiar }) {
return <input value={valor} onChange={(e) => onCambiar(e.target.value)} placeholder="Buscar..." />
}
function ListaResultados({ filtro }) {
const frutas = ['Manzana', 'Mango', 'Pera', 'Plátano', 'Melón']
const visibles = frutas.filter((f) => f.toLowerCase().includes(filtro.toLowerCase()))
return <ul>{visibles.map((f) => <li key={f}>{f}</li>)}</ul>
}Caso real: menú desplegable accesible
jsx
import { useState } from 'react'
export default function MenuUsuario() {
const [abierto, setAbierto] = useState(false)
function manejarTecla(e) {
if (e.key === 'Escape') setAbierto(false)
}
return (
<div style={{ position: 'relative', display: 'inline-block' }} onKeyDown={manejarTecla}>
<button
onClick={() => setAbierto((a) => !a)}
aria-expanded={abierto}
aria-haspopup="menu"
>
Mi cuenta ▾
</button>
{abierto && (
<ul
role="menu"
style={{
position: 'absolute', top: '110%', right: 0, listStyle: 'none', margin: 0,
padding: 8, background: 'white', border: '1px solid #ddd', borderRadius: 8, minWidth: 160
}}
>
{['Perfil', 'Pedidos', 'Cerrar sesión'].map((op) => (
<li key={op} role="none">
<button
role="menuitem"
style={{ width: '100%', textAlign: 'left', padding: 8, border: 0, background: 'none' }}
onClick={() => {
alert(op)
setAbierto(false)
}}
>
{op}
</button>
</li>
))}
</ul>
)}
</div>
)
}Buena práctica
- Usa
buttonpara acciones yapara navegar. No pongasonClicken undivpara simular un botón: no es accesible con teclado. - Nombra las props de eventos
onAlgoy los manejadoresmanejarAlgo/handleAlgo.
Error común
onClick={eliminar(id)} ejecuta la función en cada render (y puede borrar todo al cargar). Usa onClick={() => eliminar(id)}.
Ejercicios
- Crea un input que, al presionar Enter, agregue el texto a una lista y limpie el input. Escape debe limpiar sin agregar.
- Crea un componente
Acordeoncon preguntas frecuentes donde sólo una puede estar abierta a la vez (el estado vive en el padre).
Ver solución
jsx
// 1
import { useState } from 'react'
export function ListaRapida() {
const [texto, setTexto] = useState('')
const [items, setItems] = useState([])
function manejarTecla(e) {
if (e.key === 'Enter' && texto.trim()) {
setItems([...items, texto.trim()])
setTexto('')
}
if (e.key === 'Escape') setTexto('')
}
return (
<>
<input value={texto} onChange={(e) => setTexto(e.target.value)} onKeyDown={manejarTecla} />
<ul>{items.map((it, i) => <li key={i}>{it}</li>)}</ul>
</>
)
}jsx
// 2
import { useState } from 'react'
const preguntas = [
{ id: 1, q: '¿Hacen envíos?', a: 'Sí, a todo México.' },
{ id: 2, q: '¿Cuánto tarda?', a: 'De 3 a 5 días hábiles.' },
{ id: 3, q: '¿Puedo devolver?', a: 'Tienes 30 días.' }
]
function Item({ pregunta, abierto, onToggle }) {
return (
<div style={{ borderBottom: '1px solid #ddd' }}>
<button onClick={onToggle} aria-expanded={abierto} style={{ width: '100%', textAlign: 'left', padding: 12 }}>
{pregunta.q} {abierto ? '−' : '+'}
</button>
{abierto && <p style={{ padding: '0 12px' }}>{pregunta.a}</p>}
</div>
)
}
export function Acordeon() {
const [abiertoId, setAbiertoId] = useState(null)
return preguntas.map((p) => (
<Item
key={p.id}
pregunta={p}
abierto={abiertoId === p.id}
onToggle={() => setAbiertoId(abiertoId === p.id ? null : p.id)}
/>
))
}Resumen
- Los eventos se escriben en camelCase y reciben una función:
onClick={manejar}. - Para pasar argumentos usa una función flecha:
onClick={() => borrar(id)}. e.preventDefault()evita el comportamiento por defecto;e.stopPropagation()detiene el burbujeo.- Hijo → padre: el padre pasa una función
onAlgoy el hijo la llama. Levanta el estado al padre común.