Keys y Renderizado de Listas
Hasta ahora hemos trabajado con componentes individuales. En esta clase aprenderemos a renderizar listas de datos dinámicamente usando .map(), y entenderemos por qué React necesita que cada elemento tenga una key única.
El problema: Componentes repetidos manualmente
Imagina que queremos mostrar una lista de trabajos:
function App() {
return (
<div>
<article className="job-listing-card">
<h3>Frontend Developer</h3>
<p>TechCorp</p>
<p>Madrid, España</p>
</article>
<article className="job-listing-card">
<h3>Backend Developer</h3>
<p>DataStack</p>
<p>Barcelona, España</p>
</article>
<article className="job-listing-card">
<h3>Full Stack Developer</h3>
<p>StartupX</p>
<p>Valencia, España</p>
</article>
</div>
)
}
Problemas:
- 📝 Repetitivo - Copiar y pegar el mismo HTML
- 🔢 No escala - ¿Y si tienes 100 trabajos?
- 📊 Datos hardcodeados - Los datos están mezclados con la UI
- 🔄 Difícil de actualizar - Cambiar algo requiere tocar múltiples lugares
Creando el archivo de datos
Primero, creemos un archivo JSON con nuestros datos:
// src/data/jobs.json
[
{
"id": "7a4d1d8b-1e45-4d8c-9f1a-8c2f9a9121a4",
"titulo": "Desarrollador de Software Senior",
"empresa": "Tech Solutions Inc.",
"ubicacion": "Remoto",
"descripcion": "Buscamos un ingeniero de software con experiencia en desarrollo web y conocimientos en JavaScript, React y Node.js. El candidato ideal debe ser capaz de trabajar en equipo y tener buenas habilidades de comunicación.",
"data": {
"technology": ["react", "node", "javascript"],
"modalidad": "remoto",
"nivel": "senior"
}
},
{
"id": "d35b2c89-5d60-4f26-b19a-6cfb2f1a0f57",
"titulo": "Analista de Datos",
"empresa": "Data Driven Co.",
"ubicacion": "Ciudad de México",
"descripcion": "Estamos buscando un analista de datos con experiencia en el manejo de grandes conjuntos de datos y herramientas de visualización. Se requiere conocimiento en SQL, Python y R.",
"data": {
"technology": "python",
"modalidad": "cdmx",
"nivel": "junior"
}
}
// ... más elementos
]
Creando el componente JobCard
Ahora creemos un componente para mostrar cada trabajo:
// src/components/JobCard.jsx
export function JobCard({ job }) {
return (
<article
className="job-listing-card"
data-modalidad={job.data.modalidad}
data-nivel={job.data.nivel}
data-technology={job.data.technology}
>
<div>
<h3>{job.titulo}</h3>
<small>
{job.empresa} | {job.ubicacion}
</small>
<p>{job.descripcion}</p>
</div>
<button>Aplicar</button>
</article>
)
}
Puntos clave:
- Recibe
jobcomo prop - Accede a las propiedades del objeto:
job.title,job.company, etc.
Renderizando la lista con .map()
Ahora creemos el componente que renderiza la lista de trabajos:
// src/components/JobListings.jsx
import { JobCard } from './JobCard'
import jobsData from './data/jobs.json'
export function JobListings() {
return (
<div className="jobs-grid">
{jobsData.map((job) => (
<JobCard key={job.id} job={job} />
))}
</div>
)
}
¿Qué hace .map()?
// Array de datos
const jobs = [
{ id: 1, title: 'Frontend Developer' },
{ id: 2, title: 'Backend Developer' },
{ id: 3, title: 'Full Stack Developer' },
]
// .map() transforma cada elemento
jobs.map((job) => <JobCard key={job.id} job={job} />)
// Resultado: Array de componentes React
// [
// <JobCard key={1} job={{...}} />,
// <JobCard key={2} job={{...}} />,
// <JobCard key={3} job={{...}} />
// ]
¿Por qué necesitamos la key?
La prop key es obligatoria cuando renderizas listas. Sin ella, verás este warning:
⚠️ Warning: Each child in a list should have a unique "key" prop.
¿Qué es la key?
La key es un identificador único que ayuda a React a identificar qué elementos cambiaron, se agregaron o se eliminaron.
{
jobs.map((job) => <JobCard key={job.id} job={job} />)
}
// ↑
// Obligatoria y única
¿Por qué React necesita keys?
React necesita saber qué elemento es cuál cuando la lista cambia.
Sin keys: React no sabe qué cambió
// Renderizado inicial
[
<JobCard />, // Frontend Developer
<JobCard />, // Backend Developer
<JobCard />, // Full Stack Developer
]
// Agregas uno al inicio
[
<JobCard />, // 🆕 DevOps Engineer
<JobCard />, // Frontend Developer
<JobCard />, // Backend Developer
<JobCard />, // Full Stack Developer
]
// React piensa: "¿Todos cambiaron? Re-renderizo todos" 🐌
Con keys: React sabe exactamente qué cambió
// Renderizado inicial
[
<JobCard key={1} />, // Frontend Developer
<JobCard key={2} />, // Backend Developer
<JobCard key={3} />, // Full Stack Developer
]
// Agregas uno al inicio
[
<JobCard key={4} />, // 🆕 DevOps Engineer (NUEVO)
<JobCard key={1} />, // Frontend Developer (mismo)
<JobCard key={2} />, // Backend Developer (mismo)
<JobCard key={3} />, // Full Stack Developer (mismo)
]
// React piensa: "Solo key={4} es nuevo, solo renderizo ese" ⚡
Reglas de las keys
1. Deben ser únicas entre hermanos
// ✅ Correcto: IDs únicos
{
jobs.map((job) => <JobCard key={job.id} job={job} />)
}
// ❌ Error: Todas tienen la misma key
{
jobs.map((job) => <JobCard key="job" job={job} />)
}
2. Deben ser estables
La key no debería cambiar entre renderizados:
// ❌ Mal: Math.random() genera keys diferentes cada vez
{
jobs.map((job) => <JobCard key={Math.random()} job={job} />)
}
// ✅ Bien: El ID no cambia
{
jobs.map((job) => <JobCard key={job.id} job={job} />)
}
3. Preferiblemente, usa IDs del dato
// ✅ Mejor: ID del objeto
{
jobs.map((job) => <JobCard key={job.id} job={job} />)
}
// ⚠️ Aceptable solo si la lista NUNCA cambia
{
jobs.map((job, index) => <JobCard key={index} job={job} />)
}
¿Cuándo usar índices como keys?
Los índices del array (index) solo son seguros cuando:
- ✅ La lista nunca se reordena
- ✅ La lista nunca cambia (no se agregan/eliminan items)
- ✅ Los items no tienen un ID único
Ejemplo donde es seguro:
const DAYS_OF_WEEK = ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes']
// ✅ OK: Lista estática que nunca cambia
{
DAYS_OF_WEEK.map((day, index) => <li key={index}>{day}</li>)
}
Ejemplo donde NO es seguro:
const [tasks, setTasks] = useState([...])
// ❌ Mal: Lista dinámica que cambia
{tasks.map((task, index) => (
<Task key={index} task={task} />
))}
// ✅ Bien: Usa ID único
{tasks.map((task) => (
<Task key={task.id} task={task} />
))}
Problema con índices: Ejemplo visual
Imagina una lista de tareas:
// Lista inicial
;[
{ id: 'a', text: 'Tarea A', completed: false },
{ id: 'b', text: 'Tarea B', completed: true },
{ id: 'c', text: 'Tarea C', completed: false },
]
Con índices como key:
// Renderizado inicial
<Task key={0} text="Tarea A" completed={false} />
<Task key={1} text="Tarea B" completed={true} />
<Task key={2} text="Tarea C" completed={false} />
// Usuario elimina "Tarea A"
<Task key={0} text="Tarea B" completed={true} /> // ← key cambió!
<Task key={1} text="Tarea C" completed={false} /> // ← key cambió!
// React piensa: "key={0} cambió de 'Tarea A' a 'Tarea B'"
// Puede causar bugs en el estado o animaciones
Con IDs como key:
// Renderizado inicial
<Task key="a" text="Tarea A" completed={false} />
<Task key="b" text="Tarea B" completed={true} />
<Task key="c" text="Tarea C" completed={false} />
// Usuario elimina "Tarea A"
<Task key="b" text="Tarea B" completed={true} /> // ← key no cambió
<Task key="c" text="Tarea C" completed={false} /> // ← key no cambió
// React piensa: "key='a' se eliminó, las demás siguen igual"
// Todo funciona correctamente
Lista vacía: Renderizado condicional
¿Qué pasa si no hay trabajos? Deberíamos mostrar un mensaje:
export function JobListings({ jobs }) {
if (jobs.length === 0) {
return (
<div className="no-jobs">
<p>No hay trabajos disponibles en este momento.</p>
</div>
)
}
return (
<div className="jobs-grid">
{jobs.map((job) => (
<JobCard key={job.id} job={job} />
))}
</div>
)
}
O con operador ternario:
export function JobListings({ jobs }) {
return (
<div>
{jobs.length === 0 ? (
<p>No hay trabajos disponibles.</p>
) : (
<div className="jobs-grid">
{jobs.map((job) => (
<JobCard key={job.id} job={job} />
))}
</div>
)}
</div>
)
}
Lo que hemos visto en esta clase
- 🗂️ Importar JSON - Cargar datos desde archivos externos
- 🧩 Componente JobCard - Mostrar un trabajo individual
- 📋 Componente JobListings - Renderizar lista de trabajos
- 🔄
.map()- Transformar array de datos en array de componentes - 🔑 Key obligatoria - React necesita identificar elementos únicos
- 🎯 Keys únicas y estables - Usar IDs en lugar de índices
- ⚠️ Problema con índices - Por qué no usar
indexen listas dinámicas - 📊 Flujo de datos - JSON → JobListings → JobCard
- 🎭 Renderizado condicional - Mostrar mensaje si lista está vacía
En la próxima clase vamos a terminar de hacer funcionar nuestra paginación.
💡 Recuerda: Siempre usa keys únicas y estables al renderizar listas. Prefiere IDs del objeto sobre índices del array. La key ayuda a React a optimizar los renderizados identificando qué elementos cambiaron.