Hook useId()
En esta clase vamos a mejorar nuestro componente de formulario de búsqueda. Aprenderemos a manejar el evento submit y a usar el hook useId() para generar identificadores únicos que eviten colisiones en nuestros elementos HTML.
El formulario de búsqueda actual
Actualmente tenemos un formulario con dos <select> para filtrar trabajos:
// src/components/SearchForm.jsx
export function SearchForm() {
return (
<form className="search-form">
<div className="form-group">
<label htmlFor="filter-location">Ubicación</label>
<select name="location" id="filter-location">
<option value="">Ubicación</option>
<option value="remoto">Remoto</option>
<option value="cdmx">Ciudad de México</option>
<option value="guadalajara">Guadalajara</option>
<option value="monterrey">Monterrey</option>
<option value="barcelona">Barcelona</option>
</select>
</div>
<div className="form-group">
<label htmlFor="filter-experience-level">Nivel de experiencia</label>
<select name="experience-level" id="filter-experience-level">
<option value="">Nivel de experiencia</option>
<option value="junior">Junior</option>
<option value="mid">Mid-level</option>
<option value="senior">Senior</option>
<option value="lead">Lead</option>
</select>
</div>
</form>
)
}
Añadiendo el manejador de submit
Vamos a añadir un manejador para el evento submit del formulario:
// src/components/SearchForm.jsx
export function SearchForm() {
const handleSubmit = (e) => {
e.preventDefault()
console.log('Formulario enviado')
}
return (
<form className="search-form" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="filter-location">Ubicación</label>
<select name="location" id="filter-location">
<option value="">Ubicación</option>
<option value="remoto">Remoto</option>
<option value="cdmx">Ciudad de México</option>
<option value="guadalajara">Guadalajara</option>
<option value="monterrey">Monterrey</option>
<option value="barcelona">Barcelona</option>
</select>
</div>
<div className="form-group">
<label htmlFor="filter-experience-level">Nivel de experiencia</label>
<select name="experience-level" id="filter-experience-level">
<option value="">Nivel de experiencia</option>
<option value="junior">Junior</option>
<option value="mid">Mid-level</option>
<option value="senior">Senior</option>
<option value="lead">Lead</option>
</select>
</div>
</form>
)
}
Puntos clave:
onSubmit={handleSubmit}- Ejecuta la función cuando se envía el formularioe.preventDefault()- Evita que el formulario recargue la página (comportamiento por defecto)console.log()- Para verificar que el evento se está ejecutando
¿Qué es e.preventDefault()?
Cuando un formulario se envía, el navegador por defecto:
- Recarga la página
- Envía los datos a la URL especificada en
action - Pierde todo el estado de React
Ejemplo sin preventDefault():
const handleSubmit = (e) => {
console.log('Formulario enviado')
// La página se recarga inmediatamente
// El console.log puede no verse
}
Con preventDefault():
const handleSubmit = (e) => {
e.preventDefault() // ← Evita la recarga
console.log('Formulario enviado')
// Ahora podemos manejar el formulario con JavaScript
}
Añadiendo un botón de submit
Para poder enviar el formulario, necesitamos un botón:
// src/components/SearchForm.jsx
export function SearchForm() {
const handleSubmit = (e) => {
e.preventDefault()
console.log('Formulario enviado')
}
return (
<form className="search-form" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="filter-location">Ubicación</label>
<select name="location" id="filter-location">
<option value="">Ubicación</option>
<option value="remoto">Remoto</option>
<option value="cdmx">Ciudad de México</option>
<option value="guadalajara">Guadalajara</option>
<option value="monterrey">Monterrey</option>
<option value="barcelona">Barcelona</option>
</select>
</div>
<div className="form-group">
<label htmlFor="filter-experience-level">Nivel de experiencia</label>
<select name="experience-level" id="filter-experience-level">
<option value="">Nivel de experiencia</option>
<option value="junior">Junior</option>
<option value="mid">Mid-level</option>
<option value="senior">Senior</option>
<option value="lead">Lead</option>
</select>
</div>
<button type="submit">Buscar</button>
</form>
)
}
Tipos de botones:
<button type="submit">Buscar</button>
// ✅ Envía el formulario (ejecuta onSubmit)
<button type="button">Cancelar</button>
// ✅ No envía el formulario (solo ejecuta onClick)
<button type="reset">Limpiar</button>
// ✅ Resetea el formulario a sus valores iniciales
<button>Buscar</button>
// ⚠️ Por defecto es type="submit" dentro de un <form>
Importante: Si no especificas type, un <button> dentro de un <form> es automáticamente type="submit".
El problema con IDs manuales
Actualmente, nuestros IDs están hardcodeados:
<label htmlFor="filter-location">Ubicación</label>
<select name="location" id="filter-location">
{/* ... */}
</select>
<label htmlFor="filter-experience-level">Nivel de experiencia</label>
<select name="experience-level" id="filter-experience-level">
{/* ... */}
</select>
¿Qué problema hay con esto?
Problema 1: Colisiones de IDs
Los IDs deben ser únicos en toda la página. Si usas el componente dos veces:
function App() {
return (
<>
<SearchForm />
<SearchForm />
</>
)
}
HTML generado:
<!-- Primer SearchForm -->
<label for="filter-location">Ubicación</label>
<select id="filter-location">
...
</select>
<!-- Segundo SearchForm -->
<label for="filter-location">Ubicación</label>
<select id="filter-location">
...
</select>
<!-- ❌ ID duplicado! -->
Consecuencias:
- ❌ HTML inválido (los IDs deben ser únicos)
- ❌ Al hacer clic en un label, puede enfocarse el elemento incorrecto
- ❌ Problemas con lectores de pantalla (accesibilidad)
- ❌ Errores en herramientas de testing
Problema 2: Difícil de mantener
Si necesitas cambiar el ID, debes actualizarlo en varios lugares:
// Cambiar "filter-location" por "location-filter" requiere:
<label htmlFor="filter-location">
{/* ↑ Cambiar aquí */}
</label>
<select id="filter-location">
{/* ↑ Y aquí */}
</select>
La solución: useId()
React 18 introdujo el hook useId() que genera IDs únicos automáticamente:
import { useId } from 'react'
export function SearchForm() {
const locationId = useId()
const experienceId = useId()
const handleSubmit = (e) => {
e.preventDefault()
console.log('Formulario enviado')
}
return (
<form className="search-form" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor={locationId}>Ubicación</label>
<select name="location" id={locationId}>
<option value="">Ubicación</option>
<option value="remoto">Remoto</option>
<option value="cdmx">Ciudad de México</option>
<option value="guadalajara">Guadalajara</option>
<option value="monterrey">Monterrey</option>
<option value="barcelona">Barcelona</option>
</select>
</div>
<div className="form-group">
<label htmlFor={experienceId}>Nivel de experiencia</label>
<select name="experience-level" id={experienceId}>
<option value="">Nivel de experiencia</option>
<option value="junior">Junior</option>
<option value="mid">Mid-level</option>
<option value="senior">Senior</option>
<option value="lead">Lead</option>
</select>
</div>
<button type="submit">Buscar</button>
</form>
)
}
Cambios realizados:
- Importamos
useIdde React - Llamamos
useId()para cada campo que necesita un ID - Usamos los IDs generados en
htmlForyid
Nota sobre el atributo name
También podríamos usar useId() para los atributos name en lugar de usar strings hardcodeados:
export function SearchForm() {
const locationId = useId()
const experienceId = useId()
const handleSubmit = (e) => {
e.preventDefault()
console.log('Formulario enviado')
}
return (
<form className="search-form" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor={locationId}>Ubicación</label>
<select name={locationId} id={locationId}>
{/* Ahora name también usa el ID generado */}
</select>
</div>
<div className="form-group">
<label htmlFor={experienceId}>Nivel de experiencia</label>
<select name={experienceId} id={experienceId}>
{/* Evitamos magic strings como "experience-level" */}
</select>
</div>
<button type="submit">Buscar</button>
</form>
)
}
Ventajas de usar useId() para name:
- ✅ Sin magic strings - No hay valores hardcodeados como
"location"o"experience-level" - ✅ Consistencia - El mismo valor para
idyname - ✅ Refactor seguro - No hay que buscar y reemplazar strings en múltiples lugares
Cuándo usarlo:
- ✅ Si los datos son solo para React (filtrado en cliente)
- ✅ Si procesas el formulario con JavaScript (no envías a servidor)
Para nuestro caso, como solo usaremos los datos en React para filtrar, podríamos usar useId() también para los name.
¿Qué genera useId() en el DOM?
Inspeccionando el HTML generado en el navegador:
<!-- Primer SearchForm -->
<label for=":r0:">Ubicación</label>
<select id=":r0:" name="location">
...
</select>
<label for=":r1:">Nivel de experiencia</label>
<select id=":r1:" name="experience-level">
...
</select>
<!-- Segundo SearchForm (si lo usamos dos veces) -->
<label for=":r2:">Ubicación</label>
<select id=":r2:" name="location">
...
</select>
<label for=":r3:">Nivel de experiencia</label>
<select id=":r3:" name="experience-level">
...
</select>
Características del ID generado:
:r0:,:r1:,:r2:, etc.- Formato:
:r[número]: - Los dos puntos (
:) son válidos en HTML5 - Únicos en toda la aplicación
- Estables durante el ciclo de vida del componente
¿Por qué este formato?
El formato :r0: es:
- Corto - Reduce el tamaño del HTML
- Único - Garantiza que no haya colisiones
- Estable - No cambia entre renderizados
- Compatible con SSR - Funciona en Server-Side Rendering
¿Cómo funciona useId()?
const id1 = useId() // ":r0:"
const id2 = useId() // ":r1:"
const id3 = useId() // ":r2:"
Características importantes:
1. Único por componente y renderizado
function MyComponent() {
const id1 = useId() // ":r0:"
const id2 = useId() // ":r1:"
// Siempre son diferentes entre sí
console.log(id1 === id2) // false
}
2. Estable entre renderizados
function MyComponent() {
const id = useId() // ":r0:"
const [count, setCount] = useState(0)
// Aunque el componente se re-renderice, el ID no cambia
return <div id={id}>Count: {count}</div>
}
3. Único entre instancias
function App() {
return (
<>
<SearchForm /> {/* locationId: ":r0:", experienceId: ":r1:" */}
<SearchForm /> {/* locationId: ":r2:", experienceId: ":r3:" */}
</>
)
}
Comparación: ID manual vs useId()
ID Manual (❌ Problemático)
function SearchForm() {
return (
<>
<label htmlFor="location">Ubicación</label>
<select id="location" name="location">
{/* ... */}
</select>
</>
)
}
// Usarlo dos veces causa problemas
<SearchForm /> {/* id="location" */}
<SearchForm /> {/* id="location" ❌ Duplicado! */}
Problemas:
- ❌ IDs duplicados si usas el componente múltiples veces
- ❌ HTML inválido
- ❌ Bugs difíciles de detectar
- ❌ Problemas de accesibilidad
useId() (✅ Correcto)
import { useId } from 'react'
function SearchForm() {
const locationId = useId()
return (
<>
<label htmlFor={locationId}>Ubicación</label>
<select id={locationId} name="location">
{/* ... */}
</select>
</>
)
}
// Usarlo múltiples veces es seguro
<SearchForm /> {/* id=":r0:" */}
<SearchForm /> {/* id=":r1:" ✅ Único! */}
Ventajas:
- ✅ IDs únicos automáticamente
- ✅ HTML válido siempre
- ✅ Sin colisiones
- ✅ Compatible con SSR
- ✅ Accesible por defecto
Casos de uso de useId()
1. Labels y inputs
function TextField({ label }) {
const id = useId()
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} type="text" />
</>
)
}
2. ARIA attributes
function Tooltip({ children, tip }) {
const id = useId()
return (
<>
<div aria-describedby={id}>{children}</div>
<div role="tooltip" id={id}>
{tip}
</div>
</>
)
}
3. Múltiples IDs relacionados
function FormField({ label }) {
const id = useId()
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} type="text" aria-describedby={`${id}-help`} />
<small id={`${id}-help`}>Texto de ayuda</small>
</>
)
}
Nota: Puedes concatenar strings al ID generado:
const baseId = useId() // ":r0:"
const inputId = `${baseId}-input` // ":r0:-input"
const helpId = `${baseId}-help` // ":r0:-help"
Reglas de useId()
✅ Puedes:
// Llamar useId() en el nivel superior del componente
function MyComponent() {
const id = useId()
return <div id={id}>...</div>
}
// Llamarlo múltiples veces
function MyComponent() {
const id1 = useId()
const id2 = useId()
const id3 = useId()
// ...
}
// Concatenar strings al ID
const id = useId()
const inputId = `${id}-input`
const errorId = `${id}-error`
❌ No puedes:
// ❌ No usar para keys en listas
{
items.map((item) => <div key={useId()}>{item}</div>)
}
// Usa el ID del item en su lugar:
{
items.map((item) => <div key={item.id}>{item}</div>)
}
// ❌ No llamar dentro de condiciones
if (condition) {
const id = useId() // ❌ Error!
}
// ❌ No llamar dentro de loops
for (let i = 0; i < 3; i++) {
const id = useId() // ❌ Error!
}
// ❌ No llamar dentro de callbacks
const handleClick = () => {
const id = useId() // ❌ Error!
}
Regla general: useId() debe llamarse en el nivel superior del componente (como todos los hooks).
Diferencia entre useId() y otros hooks
useId() vs useState()
// ❌ No uses useState para IDs
const [id] = useState('my-id') // No es necesario
// ✅ Usa useId()
const id = useId() // Más simple y correcto
useState() es para datos que cambian. useId() es para identificadores que nunca cambian.
useRef() es para referencias a elementos DOM. useId() es específicamente para generar IDs únicos.
useId() vs Math.random()
// ❌ No uses Math.random()
const id = `input-${Math.random()}` // Cambia en cada render!
// ✅ Usa useId()
const id = useId() // Estable entre renders
Math.random() genera valores diferentes en cada renderizado. useId() es estable.
Lo que hemos visto en esta clase
- 📝 handleSubmit - Manejar el evento submit del formulario
- ⛔ e.preventDefault() - Evitar la recarga de página
- 🔘 Botón submit - Tipos de botones en formularios
- 🆔 Problema con IDs manuales - Colisiones y duplicados
- 🎣 Hook useId() - Generar IDs únicos automáticamente
- 🔍 IDs generados - Formato
:r0:,:r1:, etc. - ✅ Ventajas de useId() - Único, estable, compatible con SSR
- 🎯 Casos de uso - Labels, ARIA attributes, formularios
- 📏 Reglas de useId() - Llamar en el nivel superior del componente
- 🔄 Diferencias - useId vs useState, useRef, Math.random
En la próxima clase aprenderemos a gestionar los datos del formulario y cómo capturar los valores seleccionados por el usuario.
💡 Recuerda: Siempre usa
useId()para generar IDs en tus componentes React. Evita IDs hardcodeados para prevenir colisiones cuando el componente se usa múltiples veces. Los IDs son para accesibilidad y asociación de elementos, mientras quenamees para enviar datos del formulario.