Overview
Music Store uses React Router v6 for client-side navigation. The application has three main routes: Home, Category, and Product Detail pages.
Router Setup
The router is configured in the main App component:
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Categoria from "./pages/Categoria";
import ProductoDetalle from "./components/productos/ProductosDetalle";
import CartPanel from "./components/CartPanel/CartPanel.jsx";
function App() {
const [productos, setProductos] = useState([]);
const [cargando, setCargando] = useState(true);
// Fetch productos from Supabase
useEffect(() => {
async function fetchProductos() {
const { data, error } = await supabase.from("productos").select("*");
if (error) console.error("Error al traer productos:", error);
else setProductos(data);
setCargando(false);
}
fetchProductos();
}, []);
return (
<>
{cargando && <Loader onComplete={() => setCargando(false)} />}
<BrowserRouter>
<Routes>
<Route path="/" element={<Home productos={productos} cargando={cargando}/>} />
<Route path="/categoria/:nombre" element={<Categoria productos={productos} cargando={cargando}/>} />
<Route path="/producto/:id" element={<ProductoDetalle productos={productos} cargando={cargando}/>} />
</Routes>
<CartPanel/>
</BrowserRouter>
</>
);
}
The CartPanel component is rendered outside the <Routes> block so it appears on all pages.
Route Structure
Home
Path: /Landing page with hero section and category cards
Category
Path: /categoria/:nombreDisplays products filtered by category with brand filters
Product Detail
Path: /producto/:idShows detailed product information with add to cart button
Route Parameters
Category Route Parameter
The category route uses the :nombre parameter to determine which category to display:
<Route path="/categoria/:nombre" element={<Categoria />} />
Accessing the parameter:
import { useParams } from "react-router-dom";
function Categoria({ productos }) {
const { nombre } = useParams(); // e.g., "guitarras", "bajos", "baterias"
// Filter products by category
const productosFiltrados = productos.filter(p => p.categoria === nombre);
return (
<div>
<h1 className="capitalize">{nombre}</h1>
{/* Display filtered products */}
</div>
);
}
Example URLs:
/categoria/guitarras → shows guitars
/categoria/bajos → shows bass guitars
/categoria/baterias → shows drum kits
Product Detail Route Parameter
The product detail route uses the :id parameter to identify which product to display:
<Route path="/producto/:id" element={<ProductoDetalle />} />
Accessing the parameter:
src/components/productos/ProductosDetalle.jsx
import { useParams } from "react-router-dom";
function ProductoDetalle({ productos }) {
const { id } = useParams();
const producto = productos.find(p => p.id === Number(id));
if (!producto) return (
<div className="relative bg-[#0e0e0e] text-white min-h-screen flex items-center justify-center">
<h2 className="text-2xl text-white/50">Producto no encontrado</h2>
</div>
);
return (
<div>
<h1>{producto.nombre}</h1>
<p>${producto.precio}</p>
</div>
);
}
URL parameters are always strings. Use Number(id) to convert the string parameter to a number for comparison with product IDs.
Navigation with Link Component
Category Navigation
import { Link } from "react-router-dom";
<Link to="/categoria/guitarras" className="...">
<h3>Guitarras</h3>
</Link>
Product Navigation
Products link to their detail page using their ID:
import { Link } from "react-router-dom";
{productosFiltrados.map(prod => (
<Link
key={prod.id}
to={`/producto/${prod.id}`}
className="bg-white/5 backdrop-blur-sm rounded-2xl p-4"
>
<img src={prod.imagen} alt={prod.nombre} />
<h3>{prod.nombre}</h3>
<p>${prod.precio.toLocaleString()}</p>
</Link>
))}
Programmatic Navigation
For non-link navigation (like scrolling):
src/components/Hero/Hero.jsx
<button
onClick={() => document.getElementById("categorias").scrollIntoView({ behavior: "smooth" })}
>
Mirar Tienda
</button>
Data Flow Pattern
Centralized Data Fetching
// App.jsx - Top level
const [productos, setProductos] = useState([]);
useEffect(() => {
async function fetchProductos() {
const { data, error } = await supabase.from("productos").select("*");
if (error) console.error("Error al traer productos:", error);
else setProductos(data);
}
fetchProductos();
}, []);
Fetch Once
Products are fetched once at the app level on mount
Pass as Props
All routes receive productos array as props
Route Props
Each route receives the same props:
<Route
path="/categoria/:nombre"
element={<Categoria productos={productos} cargando={cargando}/>}
/>
Props passed to all routes:
productos: Array of all products from Supabase
cargando: Boolean indicating if data is still loading
Loading States
The app shows a loader while fetching initial data:
const [cargando, setCargando] = useState(true);
return (
<>
{cargando && <Loader onComplete={() => setCargando(false)} />}
<BrowserRouter>
<Routes>
{/* All routes receive cargando prop */}
</Routes>
</BrowserRouter>
</>
);
Page-Level Loading
function Categoria({ productos, cargando }) {
return (
<div>
{cargando ? (
<div className="flex items-center justify-center min-h-screen">
<div className="w-10 h-10 border-2 border-white/20 border-t-white rounded-full animate-spin" />
<p className="text-white/30 text-sm tracking-widest uppercase">Cargando</p>
</div>
) : (
// Display products
)}
</div>
);
}
Persistent Components
The CartPanel is rendered outside the Routes block so it persists across all pages:
<BrowserRouter>
<Routes>
{/* Page routes */}
</Routes>
<CartPanel/> {/* Always visible */}
</BrowserRouter>
Components placed outside <Routes> will not unmount during navigation, maintaining their state across page changes.
Navigation Examples
From Home to Category
// Home page category card
<Link to="/categoria/guitarras">
<h3>Guitarras</h3>
</Link>
Result: Navigates to /categoria/guitarras, which filters products where categoria === "guitarras"
From Category to Product Detail
{productosFiltrados.map(prod => (
<Link to={`/producto/${prod.id}`}>
{/* Product card */}
</Link>
))}
Result: Navigates to /producto/123, which finds the product where id === 123
Back to Home
import { Link } from "react-router-dom";
<Link to="/">
<h2>Music Store</h2>
</Link>
Error Handling
Product Not Found
src/components/productos/ProductosDetalle.jsx
const producto = productos.find(p => p.id === Number(id));
if (!producto) return (
<div className="relative bg-[#0e0e0e] text-white min-h-screen flex items-center justify-center">
<h2 className="text-2xl text-white/50">Producto no encontrado</h2>
</div>
);
- User navigates to
/producto/999 where product ID 999 doesn’t exist
find() returns undefined
- Component displays error message instead of product details
Supabase Fetch Error
const { data, error } = await supabase.from("productos").select("*");
if (error) console.error("Error al traer productos:", error);
else setProductos(data);
Errors are logged to console. The app continues with an empty products array.