← React & TypeScript

Coding challenge

create a <CampoControlado /> component that:

Challenge: create a <CampoControlado /> component that:
- Has a controlled input for an email address.
- Shows the message "Correo inválido" if the text does not include "@" (and is not empty).
- Calls the `onEnviar(correo)` prop when the form is submitted, only if the email is valid.

Tests: campo-controlado.test.tsx — try to solve it before reading the solution below.
Show solution
// 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>
  );
}