42. 8 Agregando keys.
Tutorial 42.8: Agregando Keys a los componentes React
Solucionando advertencias de React sobre Keys
En este tutorial vamos a solucionar una advertencia importante que React nos muestra en la consola cuando renderizamos listas de componentes.
El problema: Listas sin keys
Cuando abres la consola del navegador, probablemente ves este error:
text
Warning: Each child in a list should have a unique "key" prop.
Esta advertencia aparece porque React necesita una forma eficiente de identificar qué elementos han cambiado, se han agregado o se han eliminado cuando renderiza listas.
Paso 1: Identificar el problema
En nuestro ContextMovieCard.jsx, tenemos este código:
jsx
{movies.map((movie) => (
<MovieCard
movie={movie}
/>
))}
Problema: Cada MovieCard no tiene una key única.
Paso 2: Por qué necesitamos keys
React usa las keys para:
Identificar elementos únicos en una lista
Optimizar el rendimiento al actualizar solo los elementos que cambiaron
Mantener el estado de los componentes cuando la lista se reordena
Evitar bugs en la interfaz de usuario
Paso 3: Encontrar el identificador único
En la consola, cuando inspeccionamos los datos de la API, vemos que cada película tiene un id único:
javascript
{
"id": 12345,
"title": "Nombre de la Película",
"poster_path": "/imagen.jpg",
// ... más propiedades
}
El id es perfecto para usar como key porque:
Es único para cada película
No cambia
Viene directamente de la API
Paso 4: Solucionar el problema en ContextMovieCard
ContextMovieCard.jsx corregido
jsx
import { useEffect, useState } from "react";
import { get } from "../data/httpClient";
import { MovieCard } from "../components/MovieCard";
import "./ContextMovieCard.css";
export function ContextMovieCard() {
const [movies, setMovies] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
get("/discover/movie").then((data) => {
setMovies(data.results);
console.log("Películas obtenidas:", data);
setLoading(false);
});
}, []);
if (loading) {
return (
<div className="loading">
<p>Cargando películas...</p>
</div>
);
}
return (
<div className="container">
<ul className="movies-grid">
{movies.map((movie) => (
<MovieCard
key={movie.id} // ¡KEY AGREGADA AQUÍ!
movie={movie}
/>
))}
</ul>
</div>
);
}
Paso 5: Mejorando ContextMovieCard.css con margen
ContextMovieCard.css actualizado
css
/* Contenedor principal */
.container {
padding: 40px;
max-width: 1400px;
margin: 0 auto;
margin-top: 20px; /* Margen superior agregado */
}
/* Grid de películas */
.movies-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 30px;
justify-content: center;
padding: 0;
margin: 0;
list-style: none;
}
/* Estado de carga */
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
font-size: 1.2rem;
color: #ffd700;
background: rgba(106, 13, 173, 0.1);
border-radius: 10px;
margin: 40px;
margin-top: 20px; /* Margen superior agregado */
}
.loading::before {
content: "";
width: 40px;
height: 40px;
border: 4px solid rgba(255, 215, 0, 0.3);
border-top: 4px solid #ffd700;
border-radius: 50%;
margin-right: 15px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Título de la sección */
.section-title {
text-align: center;
color: #ffffff;
font-size: 2rem;
margin-top: 20px; /* Margen superior agregado */
margin-bottom: 30px;
font-weight: 600;
}
/* Responsive */
@media (max-width: 768px) {
.container {
padding: 20px;
margin-top: 15px;
}
.movies-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 20px;
}
.section-title {
margin-top: 15px;
font-size: 1.6rem;
}
}
@media (max-width: 560px) {
.movies-grid {
grid-template-columns: 1fr;
max-width: 100%;
}
.container {
padding: 15px;
margin-top: 10px;
}
.section-title {
margin-top: 10px;
font-size: 1.4rem;
}
}
Paso 6: Actualizar MovieCard.jsx
MovieCard.jsx optimizado
jsx
import "./MovieCard.css";
export function MovieCard({ movie }) {
// Construir URL de la imagen
const imageUrl = movie.poster_path
? `https://image.tmdb.org/t/p/w300${movie.poster_path}`
: "";
return (
<li className="movie-card">
<img
src={imageUrl}
alt={movie.title}
className="movie-image"
width="230"
height="345"
loading="lazy" // Optimización de carga
/>
<div className="movie-info">
<h3 className="movie-title">{movie.title}</h3>
<div className="movie-details">
<span className="rating">
⭐ {movie.vote_average.toFixed(1)}
</span>
<span className="year">
{new Date(movie.release_date).getFullYear()}
</span>
</div>
</div>
</li>
);
}
Paso 7: Mejorar MovieCard.css
MovieCard.css actualizado
css
/* Reset de estilos para lista */
.movie-card {
list-style: none;
margin: 0;
padding: 0;
font-size: 1rem;
text-align: center;
transition: all 0.5s ease;
border-radius: 15px;
overflow: hidden;
background: linear-gradient(135deg, #16213e 0%, #0f3460 100%);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
position: relative;
cursor: pointer;
height: 100%;
display: flex;
flex-direction: column;
}
/* Efecto hover */
.movie-card:hover {
transform: translateY(-10px);
box-shadow: 0 20px 40px rgba(255, 215, 0, 0.2);
z-index: 10;
}
.movie-card:hover .movie-image {
transform: scale(1.05);
}
/* Contenedor de la imagen */
.movie-image {
width: 100%;
height: 345px;
object-fit: cover;
display: block;
transition: transform 0.5s ease;
background: linear-gradient(45deg, #1a1a2e, #16213e);
}
/* Información de la película */
.movie-info {
padding: 15px 10px;
background: rgba(22, 33, 62, 0.95);
backdrop-filter: blur(10px);
border-top: 2px solid rgba(255, 215, 0, 0.3);
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.movie-title {
margin: 0 0 10px 0;
color: #ffffff;
font-size: 1rem;
font-weight: 600;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.movie-details {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.9rem;
}
.rating {
color: #ffd700;
font-weight: bold;
display: flex;
align-items: center;
gap: 5px;
}
.year {
color: #a0a0a0;
}
/* Responsive */
@media (max-width: 768px) {
.movie-card {
font-size: 0.9rem;
}
.movie-image {
height: 300px;
}
.movie-info {
padding: 12px 8px;
}
.movie-title {
font-size: 0.9rem;
margin-bottom: 8px;
}
.movie-details {
font-size: 0.8rem;
}
}
@media (max-width: 560px) {
.movie-card {
max-width: 100%;
}
.movie-card:hover {
transform: translateY(-5px);
}
}
Paso 8: Actualizar App.jsx con mejoras
App.jsx final
jsx
import './App.css';
import MyRoutes from './routers/routes';
function App() {
return (
<div className="App">
<header>
<h1 className='title'>Películas 🎬</h1>
<p className="subtitle">Descubre las mejores películas del momento</p>
</header>
<main>
<MyRoutes />
</main>
<footer className="app-footer">
<p>Datos proporcionados por TMDb API</p>
</footer>
</div>
);
}
export default App;
Paso 9: Agregar estilos al footer en App.css
App.css actualizado
css
/* ... estilos anteriores ... */
.subtitle {
text-align: center;
color: #b0b0b0;
font-size: 1.1rem;
margin-top: 5px;
font-weight: 400;
}
.app-footer {
text-align: center;
padding: 1rem;
background: rgba(22, 33, 62, 0.8);
border-top: 1px solid rgba(255, 215, 0, 0.2);
margin-top: auto;
font-size: 0.85rem;
color: #a0a0a0;
}
/* Responsive para footer */
@media (max-width: 768px) {
.subtitle {
font-size: 1rem;
}
.app-footer {
padding: 0.8rem;
font-size: 0.8rem;
}
}
¿Qué hemos logrado?
✅ Eliminamos la advertencia de keys en la consola
✅ Mejoramos el rendimiento de React al identificar elementos únicos
✅ Agregamos márgenes para mejor espaciado visual
✅ Mantenemos el estado correcto de los componentes
✅ Prevenimos bugs potenciales en la interfaz
Diferencia entre advertencias y errores
Es importante distinguir:
Advertencias (Warnings): El código funciona, pero podría ser más eficiente
Errores (Errors): El código no funciona y necesita corrección
En nuestro caso, la advertencia de keys era importante corregir para:
Mejorar el rendimiento
Evitar comportamientos inesperados
Seguir las mejores prácticas de React
Resultado final
Ahora tu aplicación:
✅ No muestra advertencias en la consola
✅ Tiene mejor espaciado visual con márgenes
✅ Es más eficiente en el renderizado
✅ Sigue las mejores prácticas de React
✅ Está lista para producción
¡Felicidades! Tu aplicación de películas ahora está optimizada y lista para el siguiente nivel
Comentarios
Publicar un comentario