hoc-lap-trinh-10

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)

By hoadv