Video CRUD Product + Register/ Login: https://www.youtube.com/watch?v=BZnYPpH7euo
Code base: https://github.com/vanhoa690/web501-ecma-react-base
Register.jsx : useForm (npm i react-hook-form)
import axios from "axios";
import { useEffect, useState } from "react";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import Header from "../components/Header";
import { useForm } from "react-hook-form";
function Register() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
console.log(errors);
const onSubmit = handleSubmit(async (data) => {
try {
await axios.post("http://localhost:3000/register", data);
toast.success("Register Successfull!");
// setTimeout(() => navigate("/login"), 1000);
} catch (error) {
console.log(error);
}
});
return (
<div>
<Header />
<div className="container w-50">
<h1 className="text-center">Register</h1>
<form onSubmit={onSubmit}>
<div className="mb-3">
<label htmlFor="email" className="form-label">
Email Address
</label>
<input
{...register("email", {
required: "Email is required",
})}
type="email"
className="form-control"
id="email"
/>
{errors?.email && (
<small className="text-danger">{errors?.email.message}</small>
)}
</div>
<div className="mb-3">
<label htmlFor="password" className="form-label">
Password
</label>
<input
{...register("password", {
required: "Password is required",
minLength: {
value: 6,
message: "Password is min length 6 characters",
},
})}
type="password"
className="form-control"
id="password"
/>
{errors?.password && (
<small className="text-danger">{errors?.password.message}</small>
)}
</div>
<button type="submit" className="btn btn-primary w-100">
Register
</button>
</form>
</div>
<ToastContainer />
</div>
);
}
export default Register;
Create Product: ProductUpdate.jsx
import axios from "axios";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
function ProductUpdate() {
// 1 state
const [productAdd, setProductAdd] = useState({
title: "",
image: "",
price: "",
});
const { id } = useParams();
//2 funct getData tu API
const getProductDetail = async (id) => {
try {
const { data } = await axios.get("http://localhost:3000/products/" + id);
// thay the setProductAdd
setProductAdd(data);
} catch (error) {
toast.error("Error!" + error.message);
console.log(error);
}
};
// 3. chay fucnt useEffect
useEffect(() => {
getProductDetail(id);
}, [id]);
// 2 handleSubmit
const handleSubmit = async (event) => {
event.preventDefault();
// call api
await axios.put("http://localhost:3000/products/" + id, productAdd);
};
//3 handleChange
const handleChange = (event) => {
setProductAdd({
...productAdd,
[event.target.name]: event.target.value,
});
};
return (
<div className="container">
<h2 className="text-center my-3">Product Update Form</h2>
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="title" className="form-label">
Title
</label>
<input
value={productAdd.title}
onChange={handleChange}
type="text"
className="form-control"
id="title"
name="title"
/>
</div>
<div className="mb-3">
<label htmlFor="image" className="form-label">
Image
</label>
<input
value={productAdd.image}
onChange={handleChange}
type="text"
className="form-control"
id="image"
name="image"
/>
</div>
<div className="mb-3">
<label htmlFor="price" className="form-label">
Price
</label>
<input
value={productAdd.price}
onChange={handleChange}
type="number"
className="form-control"
id="price"
name="price"
/>
</div>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
</div>
);
}
export default ProductUpdate;
Create Product: ProductAdd.jsx
import axios from "axios";
import { useState } from "react";
function ProductAdd() {
// 1 state
const [productAdd, setProductAdd] = useState({
title: "",
image: "",
price: "",
});
// 2 handleSubmit
const handleSubmit = async (event) => {
event.preventDefault();
// call api
await axios.post("http://localhost:3000/products", productAdd);
};
//3 handleChange
const handleChange = (event) => {
setProductAdd({
...productAdd,
[event.target.name]: event.target.value,
});
};
return (
<div className="container">
<h2 className="text-center my-3">ProductAdd Form</h2>
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="title" className="form-label">
Title
</label>
<input
value={productAdd.title}
onChange={handleChange}
type="text"
className="form-control"
id="title"
name="title"
/>
</div>
<div className="mb-3">
<label htmlFor="image" className="form-label">
Image
</label>
<input
value={productAdd.image}
onChange={handleChange}
type="text"
className="form-control"
id="image"
name="image"
/>
</div>
<div className="mb-3">
<label htmlFor="price" className="form-label">
Price
</label>
<input
value={productAdd.price}
onChange={handleChange}
type="number"
className="form-control"
id="price"
name="price"
/>
</div>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
</div>
);
}
export default ProductAdd;
App.jsx
import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import ProductList from "./pages/admin/ProductList";
import ProductDetail from "./pages/ProductDetail";
import Homepage from "./pages/Homepage";
import ProductAdd from "./pages/admin/ProductAdd";
import ProductUpdate from "./pages/admin/ProductUpdate";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="/product/:id" element={<ProductDetail />} />
<Route path="/admin/product/add" element={<ProductAdd />} />
<Route path="/admin/product/update/:id" element={<ProductUpdate />} />
<Route path="/admin/product/list" element={<ProductList />} />
</Routes>
</BrowserRouter>
);
}
export default App;
ProductDetail.jsx
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
function ProductDetail() {
const { id } = useParams();
const [product, setProduct] = useState();
const getProductDetail = async (id) => {
const res = await fetch(`http://localhost:3000/products/${id}`);
const data = await res.json();
setProduct(data);
};
useEffect(() => {
getProductDetail(id);
}, []);
// return <p>{product?.title}</p>;
return (
<div>
{product ? (
<div>
<h2>{product.title}</h2>
<img src={product.image} alt="" width="300px" />
</div>
) : (
<p>Ko tim thay Product</p>
)}
</div>
);
}
export default ProductDetail;
pages:
1.src/pages/Homepage.jsx
function Homepage() {
return <p>Homepage</p>;
}
export default Homepage;
2. src/pages/Products.jsx
import { useEffect, useState } from "react";
import ProductItem from "../components/ProductItem";
function Products() {
const [products, setProducts] = useState([]);
//call api
const getAllProducts = async () => {
const res = await fetch("http://localhost:3000/products");
const data = await res.json();
setProducts(data); // gan products = data
};
useEffect(() => {
getAllProducts();
}, []);
const handleDelete = async (id) => {
if (window.confirm("Xoa that nhe")) {
await fetch("http://localhost:3000/products/" + id, {
method: "DELETE",
});
getAllProducts();
}
};
return (
<div className="container">
<h1>Product List</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Image</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{products.map((data) => (
<ProductItem key={data.id} product={data} onDelete={handleDelete} />
))}
</tbody>
</table>
</div>
);
}
export default Products;
src/components/ProductItem.jsx
function ProductItem(props) {
//desctructing
const { product, onDelete } = props;
return (
<tr>
<td>{product.title}</td>
<td>
<img src={product.image} width="100px" />
</td>
<td>
<button
className="btn btn-danger"
onClick={() => onDelete(product.id)}
>
Delete
</button>
</td>
</tr>
);
}
export default ProductItem;
3. src/pages/ProductDetail.jsx
Create App React bằng Vite
npm create vite@latest
tên project
React
Javascript
cd project
npm i
npm run dev
https://fakestoreapi.com/products
db.json
{
"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
}
}
]
}
npm i -g json-server
npm i -g concurrently
package.json
npm start
{
...
"scripts": {
"start": "concurrently \"npx json-server db.json\" \"vite\"",
"dev": "vite",
...
},
...
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
ASM 1
Làm xong 4 bài lab: 4 điểm
Xây dựng trang Homepage ( có header, footer, danh sách products (card)): 3 điểm
Tham gia workshop tailwind: 1 – 2 điểm
Giao diện đẹp mắt: 1- 2 điểm
Lab 4:
Xây dựng trang Product Detail: 4 điểm
Sử dụng Component Header : 1 điểm
Sử dụng Component Footer: 1 điểm
Xây Route đến page: List, Detail: 1 điểm
Hiển thị thông báo nếu có lỗi, ko tìm thấy ID Product (404 Not Found): 1 điểm
Giao diện UI đẹp mắt: 2 điểm
Lab 3:
Chạy Product: BE json server + FE React (3 điểm)
Hiển màn List: Title, Image, Price, Description + Button Delete (3 điểm)
Giao diện UI đẹp mắt: (2 điểm)
Hiện thị lỗi (API URL lỗi), xóa xong hiện thị thông báo ( 2 điểm)