Lab 1 + Lab 2: Xây dựng Giao diện Trang Homepage + Product Detail
- Trang Hompage (/): có header, footer, danh sách sản phẩm, click vào product sang trang chi tiết sản phẩm – 3 điểm
- Trang ProductDetail (/product/:id): có header, footer, có thông tin chi tiết sản phẩm (tiêu đề, mô tả, giá cả, đánh giá, category …) – 3 điểm
- Có show lỗi khi call API Error và khi id product không hợp lệ hoặc không tìm thì hiện thị thông tin Product Not Found (hoặc chuyển trang Not Found) – 2 điểm
- Định nghĩa type/interface Product đầy đủ theo response API trả về – 1 điểm
- Giao diện UI đẹp mắt – 1 điểm
Create Project React + Typescript
npm create vite@latest
Đặt tên project_name
Chọn React
Chọn Typescript
cd project_name
npm i
npm i axios react-router-dom
npm run dev
File package.json: Thêm script server để chạy API: npm run server
"scripts": {
"start": "vite --open",
"server": "npx json-server db.json",
"dev": "vite",
...
}
Tạo file db.json để chạy API
{
"products": [
{
"id": 1,
"title": "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops",
"price": 109.95,
"description": "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday",
"category": "men's clothing",
"image": "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg",
"rating": {
"rate": 3.9,
"count": 120
}
},
{
"id": 2,
"title": "Mens Casual Premium Slim Fit T-Shirts ",
"price": 22.3,
"description": "Slim-fitting style, contrast raglan long sleeve, three-button henley placket, light weight & soft fabric for breathable and comfortable wearing. And Solid stitched shirts with round neck made for durability and a great fit for casual fashion wear and diehard baseball fans. The Henley style round neckline includes a three-button placket.",
"category": "men's clothing",
"image": "https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg",
"rating": {
"rate": 4.1,
"count": 259
}
}
]
}
Add routeConfig sử dụng useRoutes vào file App.tsx
import { useRoutes } from "react-router-dom";
import Homepage from "./pages/Homepage";
import ProductDetail from "./pages/ProductDetail";
const routeConfig = [
{ path: "/", element: <Homepage /> },
{ path: "/product/:id", element: <ProductDetail /> },
];
function App() {
const routes = useRoutes(routeConfig);
return <div>{routes}</div>;
}
export default App;
Thêm BrowserRouter vào file main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { BrowserRouter } from "react-router-dom";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
Tạo folder pages: tạo file pages/Homepage.tsx
import { useEffect, useState } from "react";
import axios from "axios";
type Product = {
id: string;
title: string;
image: string;
description: string;
};
function Homepage() {
const [products, setProducts] = useState<Product[]>([]);
const getAllProduct = async () => {
const { data } = await axios.get("http://localhost:3000/products");
setProducts(data);
};
useEffect(() => {
getAllProduct();
}, []);
return (
<>
{products.map((product, index) => (
<div key={index} className="card" style={{ width: "18rem" }}>
<img
src={product.image}
className="card-img-top"
alt="..."
width={"100px"}
/>
<div className="card-body">
<h5 className="card-title">{product.title}</h5>
<p className="card-text">{product.description}</p>
<a href={`/product/${product.id}`} className="btn btn-primary">
Detail
</a>
</div>
</div>
))}
</>
);
}
export default Homepage;
Tạo file pages/ProductDetail.tsx
function ProductDetail() {
return <h1>ProductDetail</h1>;
}
export default ProductDetail;