39. 5 exact path

 

Tutorial 39.5: Implementando Exact Path en React Router

Configuración correcta de rutas en React Router v6

Vamos a configurar correctamente nuestro sistema de rutas utilizando las mejores prácticas de React Router, especialmente la propiedad exact que es crucial para el enrutamiento preciso.

Paso 1: Configurar correctamente el archivo de rutas

routes.jsx

jsx

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";

import { LandingPage } from "../pages/LandingPage";


export function MyRoutes() {

    return (

        <Router>

            <Routes>

                <Route 

                    path="/" 

                    element={<LandingPage />} 

                />

            </Routes>

        </Router>

    );

}


export default MyRoutes;

Paso 2: Importar correctamente en App.jsx

App.jsx actualizado

jsx

import './App.css';

import MyRoutes from './routers/routes';


function App() {

    return (

        <div className="App">

            <header>

                <h1 className='title'>Películas 🎬</h1>

            </header>

            <main>

                <MyRoutes />

            </main>

        </div>

    );

}


export default App;

Paso 3: Importación correcta de páginas

Es crucial entender la diferencia entre páginas y componentes:

  • Páginas: Vistas completas que se muestran en rutas específicas

  • Componentes: Elementos reutilizables dentro de las páginas

Importación correcta en LandingPage.jsx

jsx

import { ContextMovieCard } from "../components/ContextMovieCard";


export function LandingPage() {

    return (

        <div>

            <ContextMovieCard />

        </div>

    );

}

Paso 4: Importar archivos CSS para mejorar la apariencia

App.css actualizado

css

/* Reset y estilos base */

* {

    margin: 0;

    padding: 0;

    box-sizing: border-box;

}


body {

    font-family: 'Poppins', sans-serif;

    background-color: #1a1a2e;

    color: #ffffff;

    line-height: 1.6;

}


/* Contenedor principal */

.App {

    max-width: 1200px;

    margin: 0 auto;

    padding: 0 1rem;

    min-height: 100vh;

}


/* Header */

header {

    padding: 2rem 1rem;

    text-align: center;

    background: linear-gradient(135deg, #16213e 0%, #0f3460 100%);

    border-bottom: 3px solid #ffd700;

    margin-bottom: 2rem;

}


.title {

    font-size: 3rem;

    color: #ffd700;

    text-shadow: 0 0 10px rgba(255, 215, 0, 0.3);

    margin-bottom: 0.5rem;

}


/* Main content */

main {

    padding: 1rem 0;

    min-height: calc(100vh - 200px);

}


/* Estilos responsive */

@media (max-width: 768px) {

    .title {

        font-size: 2rem;

    }

    

    header {

        padding: 1.5rem 1rem;

    }

}

Paso 5: Mejorar ContextMovieCard.jsx con manejo de estado

jsx

import { useEffect, useState } from 'react';

import { get } from '../data/httpClient';

import { MovieCard } from './MovieCard';

import './ContextMovieCard.css';


export function ContextMovieCard() {

    const [movies, setMovies] = useState([]);

    const [loading, setLoading] = useState(true);

    const [error, setError] = useState(null);


    useEffect(() => {

        const fetchMovies = async () => {

            try {

                setLoading(true);

                const data = await get('discover/movie');

                setMovies(data.results);

            } catch (err) {

                setError('Error al cargar las películas');

                console.error('Error:', err);

            } finally {

                setLoading(false);

            }

        };


        fetchMovies();

    }, []);


    if (loading) {

        return (

            <div className="loading-container">

                <div className="spinner"></div>

                <p>Cargando películas...</p>

            </div>

        );

    }


    if (error) {

        return (

            <div className="error-container">

                <p>{error}</p>

                <button 

                    className="retry-btn"

                    onClick={() => window.location.reload()}

                >

                    Reintentar

                </button>

            </div>

        );

    }


    return (

        <div className="context-movie-card">

            <h2 className="section-title">Películas Populares</h2>

            <div className="movies-grid">

                {movies.slice(0, 12).map((movie) => (

                    <MovieCard 

                        key={movie.id} 

                        movie={movie} 

                    />

                ))}

            </div>

        </div>

    );

}

Paso 6: ContextMovieCard.css

css

.context-movie-card {

    padding: 1rem 0;

}


.section-title {

    text-align: center;

    margin-bottom: 2rem;

    color: #ffffff;

    font-size: 1.8rem;

}


.movies-grid {

    display: grid;

    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));

    gap: 2rem;

    padding: 0 1rem;

}


/* Loading styles */

.loading-container {

    display: flex;

    flex-direction: column;

    align-items: center;

    justify-content: center;

    min-height: 300px;

    gap: 1rem;

}


.spinner {

    width: 50px;

    height: 50px;

    border: 5px solid rgba(255, 255, 255, 0.1);

    border-top: 5px solid #ffd700;

    border-radius: 50%;

    animation: spin 1s linear infinite;

}


@keyframes spin {

    0% { transform: rotate(0deg); }

    100% { transform: rotate(360deg); }

}


.loading-container p {

    color: #ffd700;

    font-size: 1.2rem;

}


/* Error styles */

.error-container {

    text-align: center;

    padding: 3rem;

    color: #ff6b6b;

}


.error-container p {

    margin-bottom: 1rem;

    font-size: 1.2rem;

}


.retry-btn {

    padding: 0.5rem 1.5rem;

    background: #ffd700;

    border: none;

    border-radius: 5px;

    color: #000;

    font-weight: bold;

    cursor: pointer;

    transition: background 0.3s ease;

}


.retry-btn:hover {

    background: #ffed4e;

}


/* Responsive */

@media (max-width: 768px) {

    .movies-grid {

        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));

        gap: 1rem;

    }

    

    .section-title {

        font-size: 1.5rem;

    }

}

Paso 7: Mejorar MovieCard.jsx

jsx

import './MovieCard.css';


export function MovieCard({ movie }) {

    // Verificar si la película tiene poster

    const posterUrl = movie.poster_path 

        ? `https://image.tmdb.org/t/p/w300${movie.poster_path}`

        : 'https://via.placeholder.com/300x450/16213e/ffffff?text=No+Poster';


    return (

        <div className="movie-card">

            <div className="poster-container">

                <img

                    src={posterUrl}

                    alt={movie.title}

                    className="movie-poster"

                />

                <div className="rating-badge">

                    ⭐ {movie.vote_average.toFixed(1)}

                </div>

            </div>

            

            <div className="movie-info">

                <h3 className="movie-title">{movie.title}</h3>

                

                <div className="movie-meta">

                    <span className="release-year">

                        {new Date(movie.release_date).getFullYear()}

                    </span>

                    <span className="language">

                        {movie.original_language.toUpperCase()}

                    </span>

                </div>

                

                <p className="movie-overview">

                    {movie.overview.length > 100 

                        ? `${movie.overview.substring(0, 100)}...` 

                        : movie.overview}

                </p>

            </div>

        </div>

    );

}

Paso 8: MovieCard.css

css

.movie-card {

    background: #16213e;

    border-radius: 10px;

    overflow: hidden;

    transition: all 0.3s ease;

    height: 100%;

    display: flex;

    flex-direction: column;

}


.movie-card:hover {

    transform: translateY(-10px);

    box-shadow: 0 15px 30px rgba(0, 0, 0, 0.4);

}


.poster-container {

    position: relative;

    height: 300px;

    overflow: hidden;

}


.movie-poster {

    width: 100%;

    height: 100%;

    object-fit: cover;

    transition: transform 0.3s ease;

}


.movie-card:hover .movie-poster {

    transform: scale(1.05);

}


.rating-badge {

    position: absolute;

    top: 10px;

    right: 10px;

    background: rgba(255, 215, 0, 0.9);

    color: #000;

    padding: 5px 10px;

    border-radius: 20px;

    font-weight: bold;

    font-size: 0.9rem;

    display: flex;

    align-items: center;

    gap: 5px;

}


.movie-info {

    padding: 1rem;

    flex-grow: 1;

    display: flex;

    flex-direction: column;

}


.movie-title {

    margin: 0 0 0.5rem 0;

    font-size: 1rem;

    color: #ffffff;

    line-height: 1.3;

    min-height: 2.6rem;

}


.movie-meta {

    display: flex;

    justify-content: space-between;

    align-items: center;

    margin-bottom: 0.8rem;

    font-size: 0.85rem;

}


.release-year {

    color: #a0a0a0;

}


.language {

    color: #ffd700;

    font-weight: bold;

    padding: 2px 8px;

    background: rgba(255, 215, 0, 0.1);

    border-radius: 4px;

    border: 1px solid rgba(255, 215, 0, 0.3);

}


.movie-overview {

    color: #b0b0b0;

    font-size: 0.85rem;

    line-height: 1.4;

    flex-grow: 1;

    margin: 0;

}


/* Responsive */

@media (max-width: 768px) {

    .poster-container {

        height: 225px;

    }

    

    .movie-title {

        font-size: 0.9rem;

        min-height: 2.2rem;

    }

    

    .movie-overview {

        font-size: 0.8rem;

    }

}

Paso 9: Agregar más rutas al sistema

Actualizar routes.jsx con más rutas

jsx

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";

import { LandingPage } from "../pages/LandingPage";

import { MovieDetailPage } from "../pages/MovieDetailPage";

import { SearchPage } from "../pages/SearchPage";

import { FavoritesPage } from "../pages/FavoritesPage";

import { NotFoundPage } from "../pages/NotFoundPage";


export function MyRoutes() {

    return (

        <Router>

            <Routes>

                {/* Ruta principal EXACTA */}

                <Route 

                    path="/" 

                    element={<LandingPage />} 

                />

                

                {/* Ruta de búsqueda */}

                <Route 

                    path="/search" 

                    element={<SearchPage />} 

                />

                

                {/* Ruta de favoritos */}

                <Route 

                    path="/favorites" 

                    element={<FavoritesPage />} 

                />

                

                {/* Ruta dinámica para detalles de película */}

                <Route 

                    path="/movie/:id" 

                    element={<MovieDetailPage />} 

                />

                

                {/* Ruta 404 - debe ir al final */}

                <Route 

                    path="*" 

                    element={<NotFoundPage />} 

                />

            </Routes>

        </Router>

    );

}


export default MyRoutes;

Paso 10: Crear páginas adicionales básicas

SearchPage.jsx

jsx

export function SearchPage() {

    return (

        <div className="search-page">

            <h2>Buscar Películas</h2>

            <p>Funcionalidad de búsqueda en desarrollo...</p>

        </div>

    );

}

FavoritesPage.jsx

jsx

export function FavoritesPage() {

    return (

        <div className="favorites-page">

            <h2>Mis Películas Favoritas</h2>

            <p>Tus películas favoritas aparecerán aquí.</p>

        </div>

    );

}

NotFoundPage.jsx

jsx

import { Link } from 'react-router-dom';


export function NotFoundPage() {

    return (

        <div className="not-found">

            <h1>404</h1>

            <h2>Página no encontrada</h2>

            <p>La página que buscas no existe o ha sido movida.</p>

            <Link to="/" className="home-link">

                Volver al inicio

            </Link>

        </div>

    );

}

Puntos clave aprendidos:

  1. Estructura correcta de rutas: Importar BrowserRouter como Router y usar Routes/Route

  2. Importación precisa: Las páginas se importan desde ../pages/ y los componentes desde ../components/

  3. Organización del código: Separación clara entre páginas y componentes

  4. Manejo de estados: Loading, error y datos en ContextMovieCard

  5. Diseño responsive: CSS Grid para el layout de películas

  6. Rutas dinámicas: /movie/:id para detalles específicos

Nota importante sobre exact en React Router v6:

En versiones anteriores de React Router se usaba exact, pero en la versión 6:

  • Las rutas ya son exactas por defecto

  • No es necesario usar la propiedad exact

  • Si necesitas rutas anidadas, debes usar /* al final del path

Comentarios

Entradas más populares de este blog

11. Creando un proyecto react con create-react-app

29. useEffect

38. 4 Routers