← React & TypeScript

Reto de código

crea un componente <CampoControlado /> que:

Reto: crea un componente <CampoControlado /> que:
- Tenga un input controlado para un correo electrónico.
- Muestre el mensaje "Correo inválido" si el texto no incluye "@" (y no está vacío).
- Llame a la prop `onEnviar(correo)` al enviar el formulario, solo si el correo es válido.

Tests: campo-controlado.test.tsx — intenta resolverlo antes de leer la solución de abajo.
Ver solución
// Reto: crea un componente <CampoControlado /> que:
// - Tenga un input controlado para un correo electrónico.
// - Muestre el mensaje "Correo inválido" si el texto no incluye "@" (y no está vacío).
// - Llame a la prop `onEnviar(correo)` al enviar el formulario, solo si el correo es válido.
//
// Tests: campo-controlado.test.tsx — intenta resolverlo antes de leer la solución de abajo.

import { useState, type ChangeEvent, type FormEvent } from "react";

type CampoControladoProps = {
  // Las funciones que vienen por props se tipan con su firma completa.
  onEnviar: (correo: string) => void;
};

export function CampoControlado({ onEnviar }: CampoControladoProps) {
  const [correo, setCorreo] = useState("");

  // El input es "controlado": su valor vive en el estado de React,
  // no en el DOM. El estado es la única fuente de la verdad.
  const esInvalido = correo.length > 0 && !correo.includes("@");

  // ChangeEvent<HTMLInputElement> le dice a TS que e.target es un input,
  // y por eso e.target.value existe y es string.
  function manejarCambio(e: ChangeEvent<HTMLInputElement>) {
    setCorreo(e.target.value);
  }

  function manejarEnvio(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    if (correo && !esInvalido) {
      onEnviar(correo);
    }
  }

  return (
    <form onSubmit={manejarEnvio}>
      <input
        type="text"
        value={correo}
        onChange={manejarCambio}
        placeholder="tu@correo.com"
      />
      {esInvalido && <p role="alert">Correo inválido</p>}
      <button type="submit">Enviar</button>
    </form>
  );
}