React y TypeScript se han convertido en una de las combinaciones más habituales del desarrollo frontend. Tipar componentes, hooks y eventos del DOM no es complicado una vez que sabes qué tipos usar dónde. Esta guía cubre los patrones más habituales en un proyecto React TypeScript real: componentes funcionales, hooks con tipos, eventos del DOM, custom hooks y forwardRef.
Componentes funcionales: JSX.Element, ReactNode y props opcionales
El tipo de retorno de un componente funcional puede ser JSX.Element, React.ReactElement o React.ReactNode. ReactNode es el más permisivo: incluye strings, numbers, arrays y null, además de JSX:
import React from "react";
interface TarjetaProps {
titulo: string;
descripcion?: string; // prop opcional
children: React.ReactNode; // cualquier contenido JSX
onClick?: () => void;
}
function Tarjeta({ titulo, descripcion, children, onClick }: TarjetaProps) {
return (
<div onClick={onClick}>
<h2>{titulo}</h2>
{descripcion && <p>{descripcion}</p>}
{children}
</div>
);
}
useState con tipos
useState infiere el tipo del estado a partir del valor inicial. Cuando el valor inicial puede ser null o el tipo es complejo, añade el tipo genérico explícitamente:
import { useState } from "react";
interface Usuario {
id: number;
nombre: string;
}
function PerfilUsuario() {
const [usuario, setUsuario] = useState<Usuario | null>(null);
const [cargando, setCargando] = useState(false); // inferido: boolean
const [contador, setContador] = useState(0); // inferido: number
async function cargar(id: number) {
setCargando(true);
const datos = await fetch(`/api/usuarios/${id}`).then(r => r.json());
setUsuario(datos as Usuario);
setCargando(false);
}
return <div>{usuario?.nombre}</div>;
}
useRef: referencias al DOM y valores mutables
useRef tiene dos usos: referencias a elementos del DOM y contenedores de valores mutables que no disparan re-renders. El tipo de genérico varía:
import { useRef, useEffect } from "react";
function CampoConFoco() {
// Referencia al DOM: tipo del elemento, inicializado a null
const inputRef = useRef<HTMLInputElement>(null);
// Valor mutable: tipo del valor, no null
const contadorRenders = useRef<number>(0);
useEffect(() => {
inputRef.current?.focus();
contadorRenders.current++;
});
return <input ref={inputRef} type="text" />;
}
Tipar eventos del DOM
Los tipos de eventos de React están en React.ChangeEvent<T>, React.FormEvent<T>, React.MouseEvent<T>, etc., donde T es el tipo del elemento HTML:
function Formulario() {
function handleChange(e: React.ChangeEvent<HTMLInputElement>): void {
console.log(e.target.value);
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>): void {
e.preventDefault();
// procesar formulario
}
function handleClick(e: React.MouseEvent<HTMLButtonElement>): void {
console.log(`Click en (${e.clientX}, ${e.clientY})`);
}
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
<button type="submit" onClick={handleClick}>Enviar</button>
</form>
);
}
Custom hooks con tuplas tipadas
function useContador(inicial = 0): [number, () => void, () => void] {
const [cuenta, setCuenta] = useState(inicial);
const incrementar = () => setCuenta(c => c + 1);
const decrementar = () => setCuenta(c => c - 1);
return [cuenta, incrementar, decrementar];
}
// Uso: TypeScript conoce exactamente los tipos de la tupla
const [cuenta, incrementar, decrementar] = useContador(0);
forwardRef
import { forwardRef } from "react";
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
etiqueta: string;
}
const CampoTexto = forwardRef<HTMLInputElement, InputProps>(
({ etiqueta, ...props }, ref) => (
<label>
{etiqueta}
<input ref={ref} {...props} />
</label>
)
);
Imagen: Pexels / Stanislav Kondratiev
