Skip to main content

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:
src/App.jsx
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:
src/pages/Categoria.jsx
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.

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:
src/pages/Categoria.jsx
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:
src/App.jsx
const [cargando, setCargando] = useState(true);

return (
  <>
    {cargando && <Loader onComplete={() => setCargando(false)} />}
    <BrowserRouter>
      <Routes>
        {/* All routes receive cargando prop */}
      </Routes>
    </BrowserRouter>
  </>
);

Page-Level Loading

src/pages/Categoria.jsx
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:
src/App.jsx
<BrowserRouter>
  <Routes>
    {/* Page routes */}
  </Routes>
  <CartPanel/>  {/* Always visible */}
</BrowserRouter>
Components placed outside <Routes> will not unmount during navigation, maintaining their state across page changes.

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

src/pages/Categoria.jsx
{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

src/App.jsx
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.

Build docs developers (and LLMs) love