npm create vite@latest my-shop -- --template react
cd my-shop
npm i react-router-dom@6
npm isrc/
├─ main.jsx ← entry point (creates root + router)
├─ App.jsx ← UI layout
├─ data/
│ └─ products.js
└─ pages/
├─ Home.jsx
└─ ProductDetail.jsx
// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App.jsx';
import './index.css'; // optional
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home.jsx';
import ProductDetail from './pages/ProductDetail.jsx';
export default function App() {
return (
<div style={{ padding: '2rem', fontFamily: 'system-ui, sans-serif' }}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products/:id" element={<ProductDetail />} />
</Routes>
</div>
);
}// src/data/products.js
const products = [
{ id: '1', name: 'Eco Water Bottle', price: 15.99, description: 'Reusable and BPA‑free.' },
{ id: '2', name: 'Wireless Headphones', price: 89.99, description: 'Noise‑cancelling, 30‑hr battery.' },
{ id: '3', name: 'Yoga Mat', price: 25.5, description: 'Non‑slip, eco‑friendly.' },
];
export default products;// src/pages/Home.jsx
import { Link } from 'react-router-dom';
import products from '../data/products';
export default function Home() {
return (
<>
<h1>Our Products</h1>
<div style={{ display: 'grid', gap: '1rem', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))' }}>
{products.map((p) => (
<Link
key={p.id}
to={`/products/${p.id}`}
style={{ textDecoration: 'none', color: 'inherit' }}
>
<div
style={{
border: '1px solid #ddd',
borderRadius: '8px',
padding: '1rem',
textAlign: 'center',
cursor: 'pointer',
transition: 'transform .2s',
}}
onMouseEnter={(e) => (e.currentTarget.style.transform = 'scale(1.03)')}
onMouseLeave={(e) => (e.currentTarget.style.transform = '')}
>
<h3 style={{ margin: '0 0 .5rem' }}>{p.name}</h3>
<p style={{ margin: 0, fontWeight: 'bold' }}>${p.price}</p>
</div>
</Link>
))}
</div>
</>
);
}// src/pages/ProductDetail.jsx
import { useParams, Link } from 'react-router-dom';
import products from '../data/products';
export default function ProductDetail() {
const { id } = useParams();
const product = products.find((p) => p.id === id);
if (!product) {
return (
<div style={{ textAlign: 'center', marginTop: '2rem' }}>
<h2>Product not found</h2>
<Link to="/">← Back to Home</Link>
</div>
);
}
return (
<div style={{ maxWidth: '600px', margin: '2rem auto' }}>
<Link to="/" style={{ display: 'inline-block', marginBottom: '1rem' }}>
← Back to Home
</Link>
<h1>{product.name}</h1>
<p>
<strong>Price:</strong> ${product.price}
</p>
<p>
<strong>Description:</strong> {product.description}
</p>
</div>
);
}npm run devOpen http://localhost:5173 → click any product → you’ll land on /products/1, /products/2, etc. and see the full detail.
| Symptom | Root cause | Fix |
|---|---|---|
| Clicking a card reloads the page and shows “Product not found” | <Link> was wrapped around the card, but the card itself contained a <div> that stopped the event (or you used <a href>). |
Keep the entire card inside a single <Link> (as shown). |
useParams() returns undefined |
Router not mounted at the top level. | Wrap the app with <BrowserRouter> in main.jsx. |
products array not found |
Wrong import path (../data/products vs ../data/products.js). |
Use the exact relative path or add a .js extension if needed. |
react-router-dom@6installed.<BrowserRouter>around<App />inmain.jsx.<Link to="/products/${id}">wraps the whole card.useParams()+products.find(p => p.id === id)inProductDetail.