TechLead

React con TypeScript

Desarrollo React con tipos seguros — props de componentes, hooks, eventos, contexto y patrones genéricos con TypeScript.

React con TypeScript

TypeScript agrega verificación de tipos estática a tu código React, detectando errores en tiempo de compilación en lugar de tiempo de ejecución. Esta guía cubre los patrones esenciales para tipar componentes, hooks, eventos y estado en proyectos modernos de React + TypeScript.

1. Tipar Props de Componentes

interface BotonProps {
  etiqueta: string;
  variante?: "primary" | "secondary" | "danger";
  deshabilitado?: boolean;
  onClick: () => void;
}

function Boton({ etiqueta, variante = "primary", deshabilitado, onClick }: BotonProps) {
  return (
    <button className={`btn btn-${variante}`} disabled={deshabilitado} onClick={onClick}>
      {etiqueta}
    </button>
  );
}

2. Props con Children

interface TarjetaProps {
  titulo: string;
  children: React.ReactNode;
}

function Tarjeta({ titulo, children }: TarjetaProps) {
  return (
    <div className="card">
      <h2>{titulo}</h2>
      <div>{children}</div>
    </div>
  );
}

3. Tipar useState

// Inferido como string
const [nombre, setNombre] = useState("");

// Tipo explícito — inicial null, luego un objeto
interface Usuario {
  id: number;
  nombre: string;
  email: string;
}

const [usuario, setUsuario] = useState<Usuario | null>(null);

// Acceso con narrowing
if (usuario) {
  console.log(usuario.nombre); // TypeScript sabe que usuario es Usuario
}

4. Tipar Eventos

function EjemplosEventos() {
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
  };

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };

  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    console.log("Click en:", e.clientX, e.clientY);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} />
      <button onClick={handleClick}>Enviar</button>
    </form>
  );
}

5. Tipar Contexto

interface AuthContextType {
  usuario: Usuario | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

function useAuth(): AuthContextType {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error("useAuth debe usarse dentro de un AuthProvider");
  }
  return context;
}

6. Componentes Genéricos

interface ListaProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
  keyExtractor: (item: T) => string | number;
}

function Lista<T>({ items, renderItem, keyExtractor }: ListaProps<T>) {
  return (
    <ul>
      {items.map((item) => (
        <li key={keyExtractor(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// Uso — TypeScript infiere T como Usuario
<Lista<Usuario>
  items={usuarios}
  keyExtractor={(u) => u.id}
  renderItem={(u) => <span>{u.nombre}</span>}
/>

7. Tipos de Utilidad para React

// Extender props de elementos HTML nativos
type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
  label: string;
  error?: string;
};

function Input({ label, error, ...inputProps }: InputProps) {
  return (
    <div>
      <label>{label}</label>
      <input {...inputProps} />
      {error && <span className="text-red-500">{error}</span>}
    </div>
  );
}

⚠️ Errores Comunes

  • No uses React.FC — incluye children implícitamente. Tipa props directamente.
  • No uses any para eventos — usa los tipos de eventos específicos de React.
  • No olvides el narrowing de tipos nullablesuseState<User | null> requiere verificaciones de null.
  • Evita aserciones de tipo (as) — prefiere type guards y tipado apropiado.