Eventos en React: interacción del usuario y actualización segura del estado

Capítulo 5

Tiempo estimado de lectura: 7 minutos

+ Ejercicio

Qué es un evento en React y cómo se diferencia del DOM

En React, los eventos son la forma estándar de responder a la interacción del usuario (clics, escritura, envío de formularios). Se declaran como props en elementos JSX usando nombres en camelCase, por ejemplo onClick, onChange y onSubmit. En lugar de pasar una cadena como en HTML, se pasa una función (un handler) que React ejecutará cuando ocurra el evento.

Idea clave: el handler debe describir “qué hacer” cuando ocurre el evento, y normalmente actualizará el estado mediante su setter. React se encarga de volver a renderizar con el nuevo estado.

onClick: responder a clics sin ejecutar en render

Patrón básico

function Counter() {
  const [count, setCount] = React.useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button type="button" onClick={handleClick}>
      Incrementar ({count})
    </button>
  );
}

Observa que onClick={handleClick} pasa la función, no la ejecuta. Si escribes onClick={handleClick()}, se ejecutará durante el render y no cuando el usuario haga clic.

Actualizar estado basado en el valor previo (forma segura)

Cuando el nuevo estado depende del anterior, usa la forma funcional del setter. Esto evita errores por actualizaciones agrupadas (batching) o múltiples cambios seguidos.

function Counter() {
  const [count, setCount] = React.useState(0);

  function handleClick() {
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
  }

  return (
    <button type="button" onClick={handleClick}>
      +2 (actual: {count})
    </button>
  );
}

Con la forma funcional, cada actualización recibe el valor más reciente, incluso si React agrupa renders.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

onChange: capturar inputs de forma controlada

Un input “controlado” es aquel cuyo valor proviene del estado, y se actualiza con onChange. Así, la UI y el estado siempre están sincronizados.

Input de texto controlado

function NameField() {
  const [name, setName] = React.useState("");

  function handleChange(e) {
    setName(e.target.value);
  }

  return (
    <div>
      <label htmlFor="name">Nombre</label>
      <input
        id="name"
        type="text"
        value={name}
        onChange={handleChange}
        autoComplete="name"
      />
      <p>Hola, {name || "(sin nombre)"}</p>
    </div>
  );
}

Guía práctica paso a paso: convertir un input a controlado

  • 1) Crea estado para el valor: const [value, setValue] = useState("").
  • 2) Asigna value={value} al <input>.
  • 3) Añade onChange y dentro usa setValue(e.target.value).
  • 4) Evita mezclar defaultValue con value en el mismo input (controlado vs. no controlado).

Checkbox y select controlados

function Preferences() {
  const [accepted, setAccepted] = React.useState(false);
  const [country, setCountry] = React.useState("ES");

  return (
    <form>
      <div>
        <input
          id="terms"
          type="checkbox"
          checked={accepted}
          onChange={e => setAccepted(e.target.checked)}
        />
        <label htmlFor="terms">Acepto los términos</label>
      </div>

      <div>
        <label htmlFor="country">País</label>
        <select
          id="country"
          value={country}
          onChange={e => setCountry(e.target.value)}
        >
          <option value="ES">España</option>
          <option value="MX">México</option>
          <option value="AR">Argentina</option>
        </select>
      </div>
    </form>
  );
}

Regla rápida: texto usa e.target.value, checkbox usa e.target.checked.

onSubmit: formularios, preventDefault y envío seguro

Al enviar un formulario, el navegador intenta recargar la página. En React, normalmente quieres evitarlo y manejar el envío con JavaScript. Para eso se usa e.preventDefault() dentro de onSubmit.

function LoginForm() {
  const [email, setEmail] = React.useState("");
  const [password, setPassword] = React.useState("");
  const [status, setStatus] = React.useState("idle");

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus("loading");

    try {
      // Simulación de llamada
      await new Promise(r => setTimeout(r, 600));
      setStatus("success");
    } catch (err) {
      setStatus("error");
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={e => setEmail(e.target.value)}
          autoComplete="email"
          required
        />
      </div>

      <div>
        <label htmlFor="password">Contraseña</label>
        <input
          id="password"
          type="password"
          value={password}
          onChange={e => setPassword(e.target.value)}
          autoComplete="current-password"
          required
        />
      </div>

      <button type="submit" disabled={status === "loading"}>
        {status === "loading" ? "Enviando..." : "Entrar"}
      </button>

      <p role="status" aria-live="polite">
        {status === "success" ? "Acceso concedido" : null}
        {status === "error" ? "Hubo un error" : null}
      </p>
    </form>
  );
}

Buenas prácticas: usa onSubmit en el <form> (no solo onClick en el botón), y define type="submit" explícitamente. Esto mejora accesibilidad y compatibilidad con teclado.

Pasar handlers como props (comunicación de hijo a padre)

Un patrón común es que el componente padre define el estado y pasa una función al hijo para que el hijo “notifique” eventos. El hijo no necesita conocer cómo se guarda el estado; solo llama al handler.

Ejemplo: lista de tareas con botón en un hijo

function TodoApp() {
  const [todos, setTodos] = React.useState([
    { id: 1, text: "Leer", done: false },
    { id: 2, text: "Practicar", done: true }
  ]);

  function toggleTodo(id) {
    setTodos(prev =>
      prev.map(t => (t.id === id ? { ...t, done: !t.done } : t))
    );
  }

  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={() => toggleTodo(todo.id)}
        />
      ))}
    </ul>
  );
}

function TodoItem({ todo, onToggle }) {
  return (
    <li>
      <label>
        <input type="checkbox" checked={todo.done} onChange={onToggle} />
        {todo.text}
      </label>
    </li>
  );
}

Detalles importantes: el padre actualiza estado de forma inmutable (map y { ...t }), y el hijo recibe un handler listo para usar. En el ejemplo, onToggle se pasa como función, no como resultado de una llamada.

Patrones seguros para actualizar estado en handlers

1) Usa actualizaciones funcionales cuando dependes del valor previo

// Bien
setCount(prev => prev + 1);

// Riesgo si hay múltiples actualizaciones seguidas
setCount(count + 1);

2) No mutes arrays u objetos; crea nuevas copias

// Mal: mutación
function addItemBad(item) {
  items.push(item);
  setItems(items);
}

// Bien: copia
function addItemGood(item) {
  setItems(prev => [...prev, item]);
}

3) Si necesitas el valor actual dentro de un callback asíncrono, evita cierres obsoletos

Un cierre (closure) puede “capturar” un valor antiguo de estado si lo usas más tarde (por ejemplo, en un setTimeout o tras un await). Para contadores o acumulaciones, prefiere la forma funcional.

function DelayedCounter() {
  const [count, setCount] = React.useState(0);

  function incrementLater() {
    setTimeout(() => {
      setCount(prev => prev + 1);
    }, 1000);
  }

  return (
    <button type="button" onClick={incrementLater}>
      Incrementar en 1s (actual: {count})
    </button>
  );
}

Accesibilidad básica en interacción

Botones reales vs. div clicable

Para acciones, usa <button> en lugar de un <div> con onClick. El botón ya incluye soporte de teclado (Enter/Espacio), roles correctos y estados como disabled.

Necesitas...Usa...Evita...
Acción (enviar, abrir, guardar)<button type="button"><div onClick>
Navegación<a href="..."> (o componente de enlace)<button> para navegar

Etiquetas en formularios

Asocia siempre <label> con su control usando htmlFor e id. Esto mejora la usabilidad (clic en la etiqueta enfoca el input) y ayuda a lectores de pantalla.

<label htmlFor="city">Ciudad</label>
<input id="city" value={city} onChange={...} />

Mensajes dinámicos anunciables

Para feedback que cambia (por ejemplo, “Guardado”, “Error”), usa una región con role="status" y aria-live="polite" para que tecnologías asistivas lo anuncien sin interrumpir.

<p role="status" aria-live="polite">{message}</p>

Errores comunes (y cómo evitarlos)

  • Llamar funciones en render: onClick={doThing()} ejecuta la función al renderizar. Solución: pasa la referencia onClick={doThing} o envuelve en una función onClick={() => doThing(arg)}.
  • Mutar estado en handlers: hacer state.push(), state.sort() o modificar propiedades directamente y luego llamar al setter con el mismo objeto/array. Solución: crea copias inmutables ([...prev], prev.map, {...prev}).
  • Cierres (closures) con valores obsoletos: usar count dentro de callbacks asíncronos y asumir que es el valor más reciente. Solución: usa setters funcionales (setCount(prev => ...)) o reestructura para leer el valor actual en el momento correcto.

Ahora responde el ejercicio sobre el contenido:

Si en un handler necesitas actualizar un contador dos veces y evitar errores cuando React agrupa actualizaciones, ¿qué enfoque es el más seguro?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Cuando el nuevo estado depende del anterior, la forma funcional (prev => prev + 1) es más segura porque cada actualización usa el valor más reciente, incluso si React agrupa renders o se hacen múltiples cambios seguidos.

Siguiente capítulo

Renderizado condicional en React: estados de UI, ramas y guardas

Arrow Right Icon
Portada de libro electrónico gratuitaReact para principiantes: mentalidad de componentes y manejo de estado
42%

React para principiantes: mentalidad de componentes y manejo de estado

Nuevo curso

12 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.