Skip to main content

Overview

The movie detail page is the central hub for all information about a specific film. It displays comprehensive metadata, allows users to watch trailers, and provides options to rent or purchase the movie.

Page Structure

The detail page combines visual and textual information in an intuitive layout:

Left Panel

Large movie poster with high-quality imagery

Right Panel

Complete metadata, synopsis, cast, and action buttons

Implementation

The PeliculaDetailPage component handles routing, data loading, and user interactions:
import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import RentalButton from '../components/AlquilerButton';
import PurchaseButton from '../components/CompraButton';
import TrailerModal from '../components/TrailerModal';
import LoadingSpinner from '../components/LoadingSpinner';
import { mockPeliculas } from '../data/mockPeliculas';

const PeliculaDetailPage = () => {
  const { id } = useParams();
  const navigate = useNavigate();
  const [pelicula, setPelicula] = useState(null);
  const [loading, setLoading] = useState(true);
  const [showTrailer, setShowTrailer] = useState(false);
  const [alquileres, setAlquileres] = useState([]);
  const [compras, setCompras] = useState([]);

  // Load movie data and localStorage state
  useEffect(() => {
    const timer = setTimeout(() => {
      const foundPelicula = mockPeliculas.find(m => m.id === parseInt(id));
      if (foundPelicula) {
        setPelicula(foundPelicula);
      }
      setLoading(false);
    }, 500);

    const savedAlquileres = JSON.parse(localStorage.getItem('alquileres') || '[]');
    const savedCompras = JSON.parse(localStorage.getItem('compras') || '[]');
    setAlquileres(savedAlquileres);
    setCompras(savedCompras);

    return () => clearTimeout(timer);
  }, [id]);

  // ... handler functions
};

URL Routing

The page uses React Router to capture the movie ID from the URL:
/pelicula/:id → /pelicula/1 (Inception)
/pelicula/:id → /pelicula/4 (The Dark Knight)
The useParams hook extracts the ID, which is then used to find the matching movie from the data:
const { id } = useParams();
const foundPelicula = mockPeliculas.find(m => m.id === parseInt(id));
The ID from the URL is a string, so it’s converted to an integer before comparison.

Loading State

A simulated 500ms loading delay provides realistic UX:
const timer = setTimeout(() => {
  const foundPelicula = mockPeliculas.find(m => m.id === parseInt(id));
  if (foundPelicula) {
    setPelicula(foundPelicula);
  }
  setLoading(false);
}, 500);

if (loading) {
  return <LoadingSpinner />;
}
This prevents jarring instant loads and gives the app a more polished feel.

Not Found Handling

If the movie ID doesn’t exist, users see a helpful error page:
if (!pelicula) {
  return (
    <div className="pelicula-detail__not-found">
      <h2>Película no encontrada</h2>
      <button 
        onClick={() => navigate('/peliculas')} 
        className="pelicula-detail__back-btn"
      >
        Volver al catálogo
      </button>
    </div>
  );
}
The “back to catalog” button uses React Router’s navigate function for instant, client-side navigation.

Information Display

The detail page shows comprehensive movie information:

Hero Section

<div className="pelicula-detail__container">
  <div className="pelicula-detail__image-container">
    <img 
      src={pelicula.image} 
      alt={pelicula.title}
      className="pelicula-detail__image"
    />
  </div>

  <div className="pelicula-detail__info">
    <h1 className="pelicula-detail__title">{pelicula.title}</h1>
    
    <div className="pelicula-detail__meta">
      <span className="pelicula-detail__year">{pelicula.year}</span>
      <span className="pelicula-detail__genre">{pelicula.genre}</span>
      <span className="pelicula-detail__duration">{pelicula.duration} min</span>
      <span className="pelicula-detail__rating">{pelicula.rating}/10</span>
    </div>
  </div>
</div>

Metadata Fields

Director

Full director name(s)

Cast

Comma-separated list of main actors

Language

Original audio language

Director & Cast

<div className="pelicula-detail__director">
  <strong>Director:</strong> {pelicula.director}
</div>

<div className="pelicula-detail__cast">
  <strong>Reparto:</strong> {pelicula.cast.join(', ')}
</div>

<div className="pelicula-detail__language">
  <strong>Idioma:</strong> {pelicula.language}
</div>
Example Output:
  • Director: Christopher Nolan
  • Reparto: Leonardo DiCaprio, Joseph Gordon-Levitt, Elliot Page
  • Idioma: Inglés

Synopsis

<div className="pelicula-detail__synopsis">
  <h3>Sinopsis</h3>
  <p>{pelicula.synopsis}</p>
</div>
The synopsis provides a brief plot description to help users decide if they want to watch the movie.

Action Buttons

Three primary actions are available on the detail page:
<div className="pelicula-detail__actions">
  <button 
    className="pelicula-detail__trailer-btn"
    onClick={() => setShowTrailer(true)}
  >
    Ver Tráiler
  </button>
  <RentalButton pelicula={pelicula} onRent={handleRent} />
  <PurchaseButton pelicula={pelicula} onPurchase={handlePurchase} />
</div>

1. Watch Trailer

Clicking “Ver Tráiler” opens a modal with an embedded YouTube video:
const [showTrailer, setShowTrailer] = useState(false);

// In JSX:
{showTrailer && (
  <TrailerModal 
    pelicula={pelicula} 
    onClose={() => setShowTrailer(false)} 
  />
)}
The trailer modal:
  • Embeds YouTube iframe with the movie’s trailer
  • Locks body scrolling while open
  • Closes on ESC key or backdrop click
  • Provides immersive full-screen experience
Trailer URLs are stored as YouTube embed links (e.g., “https://www.youtube.com/embed/YoHD9XEInc0”).

2. Rent Movie

The RentalButton allows 48-hour rentals:
const handleRent = (rentedPelicula) => {
  const newAlquileres = [
    ...alquileres, 
    { ...rentedPelicula, alquilerDate: new Date().toISOString() }
  ];
  setAlquileres(newAlquileres);
  localStorage.setItem('alquileres', JSON.stringify(newAlquileres));
};
User Experience:
  1. Click “Alquilar por S/ 3.99”
  2. Button shows “Procesando…”
  3. Alert confirms rental
  4. Movie added to “Mis Alquileres”

3. Purchase Movie

The PurchaseButton enables permanent ownership:
const handlePurchase = (compradPelicula) => {
  const newCompras = [
    ...compras, 
    { ...compradPelicula, compraDate: new Date().toISOString() }
  ];
  setCompras(newCompras);
  localStorage.setItem('compras', JSON.stringify(newCompras));
};
User Experience:
  1. Click “Comprar por €12.99”
  2. Button shows “Procesando…”
  3. Alert confirms purchase
  4. Movie added to “Mis Compras”
Both rental and purchase handlers add timestamp metadata (alquilerDate/compraDate) for tracking.

LocalStorage Integration

The detail page reads and writes to localStorage for persistence:

On Page Load

useEffect(() => {
  const savedAlquileres = JSON.parse(localStorage.getItem('alquileres') || '[]');
  const savedCompras = JSON.parse(localStorage.getItem('compras') || '[]');
  setAlquileres(savedAlquileres);
  setCompras(savedCompras);
}, [id]);

On Transaction

// For rentals
localStorage.setItem('alquileres', JSON.stringify(newAlquileres));

// For purchases
localStorage.setItem('compras', JSON.stringify(newCompras));
This ensures transactions persist across page reloads and browser sessions. Users can reach the detail page from multiple entry points:

From Catalog

// In PeliculaCard component
<Link to={`/pelicula/${pelicula.id}`} className="pelicula-card__details-btn">
  Ver detalles
</Link>

From Other Pages

  • Search Results: Click any movie card
  • Rentals Page: View details of rented movies
  • Purchases Page: Access owned movie details
  • Direct Link: Share URL like /pelicula/1

Responsive Behavior

The layout adapts to different screen sizes:
  • Desktop: Side-by-side poster and info panels
  • Tablet: Stacked layout with smaller poster
  • Mobile: Single column, full-width elements

Example: Inception Detail Page

Here’s what users see when viewing Inception (ID: 1):
URL: /pelicula/1

┌─────────────────────────────────────────────────────┐
│  [Large Poster]    INCEPTION                        │
│                    2010 • Ciencia Ficción           │
│                    148 min • ⭐ 8.8/10               │
│                                                     │
│                    Director: Christopher Nolan      │
│                    Reparto: Leonardo DiCaprio,      │
│                    Joseph Gordon-Levitt, Elliot Page│
│                    Idioma: Inglés                   │
│                                                     │
│                    Sinopsis                         │
│                    Un ladrón que roba secretos...   │
│                                                     │
│                    [Ver Tráiler]                    │
│                    [Alquilar por S/ 3.99]           │
│                    [Comprar por €12.99]             │
└─────────────────────────────────────────────────────┘

Performance Optimization

The page includes several performance considerations:

Simulated Loading

const timer = setTimeout(() => {
  // Fetch movie
  setLoading(false);
}, 500);

return () => clearTimeout(timer);
The cleanup function prevents memory leaks if users navigate away before loading completes.

Conditional Rendering

Components render only when needed:
{showTrailer && <TrailerModal ... />}
The trailer modal doesn’t exist in the DOM until activated, saving resources.

Early Returns

if (loading) return <LoadingSpinner />;
if (!pelicula) return <NotFoundPage />;
// Main render only if movie exists
This pattern prevents unnecessary rendering of the main UI.

Accessibility Features

  • Semantic HTML: Proper heading hierarchy (h1, h3)
  • Alt Text: Movie title used as image alt
  • Button Labels: Clear action descriptions
  • Keyboard Support: All buttons keyboard accessible
  • Focus Management: Trailer modal handles focus properly

Error Handling

Image Load Failures

While the detail page doesn’t have explicit image error handling, the catalog’s PeliculaCard provides a pattern that could be implemented:
const [imageError, setImageError] = useState(false);

<img 
  src={imageError ? 'fallback.jpg' : pelicula.image}
  onError={() => setImageError(true)}
/>

Invalid IDs

The not-found handler manages invalid or non-existent movie IDs gracefully.

Future Enhancements

  • Reviews & Ratings: User-generated reviews
  • Related Movies: Recommendations based on genre/director
  • Availability Status: Show if already rented/purchased
  • Social Sharing: Share movie to social media
  • Watchlist: Add to watchlist without renting/buying
  • Quality Options: HD, 4K badges and pricing
  • Subtitles Info: Available subtitle languages
  • Age Rating: MPAA/PEGI rating display

Technical Details

Component Dependencies

import { useParams, useNavigate } from 'react-router-dom';
import RentalButton from '../components/AlquilerButton';
import PurchaseButton from '../components/CompraButton';
import TrailerModal from '../components/TrailerModal';
import LoadingSpinner from '../components/LoadingSpinner';
import { mockPeliculas } from '../data/mockPeliculas';

State Management

  • pelicula: Current movie data
  • loading: Loading state for spinner
  • showTrailer: Modal visibility toggle
  • alquileres: User’s rented movies
  • compras: User’s purchased movies

Side Effects

  • useEffect with [id] dependency loads movie on route change
  • Cleanup function clears timeout on unmount
  • localStorage reads on mount, writes on transaction

Next Steps

Movie Catalog

Return to the catalog where users discover movies

Search & Filter

Learn how users find specific movies

Build docs developers (and LLMs) love