Trong bài viết trước, chúng ta đã hoàn thiện ứng dụng “File management” phía backend sử dụng SpringBoot, JPA + test các API trong ứng dụng bằng Postman (các bạn có thể xem lại bài viết tại đây)
Trong bài viết này, chúng ta sẽ tiếp tục bổ sung thêm giao diện người dùng sử dụng thư viện React kết nối với các API đã thực hiện trước đó, để hoàn thành hoàn chỉnh ứng dụng
Setup CORS backend
CORS (Cross-origin resource sharing) là một cơ chế cho phép hạn chế việc chia sẻ tài nguyên của một trang web đối với các trang web khác.
Ví dụ : web A chỉ cho phép các request từ trang web A này yêu cầu tài nguyên, các trang web B và C sẽ bị hạn chế. Nếu cố truy cập sẽ báo lỗi CORS.
Đây là một cơ chế bảo mật của trình duyệt, khi không sử dụng CORS thì bất cứ trang web nào cũng có thể thực hiện requests tới trang web đó, từ đó gây ra lỗi tài nguyên hệ thống.
Để enable CORS trong SpringBoot chúng ta có thể thực hiện như sau
package vn.techmaster.image.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE");
}
}
WebConfig.java
Tạo project
Để tạo project React chúng ta có thể sử dụng nhiều cách, ở đây mình sử dụng vite
. Đây là công cụ build tool được giới thiệu là vô cùng mạnh mẽ, có tốc độ kinh hoàng. Các bạn có thể tham khảo document của nó ở đây : https://vitejs.dev/
Câu lệnh để tạo ứng dụng với vite
như sau:
$ npm create vite@latest file-management-frontend
Sau khi chạy xong câu lệnh trên, nó sẽ hiển thị các gợi ý về framework và variant
Chúng ta sẽ chọn framework : React và variant : Javascript
Mở project vừa tạo, tiến hành download các dependence cần thiết cho ứng dụng react và chạy project
$ npm install // download các dependence
$ npm run dev // chạy project
Bổ sung thêm các dependence
Ngoài các dependence cần thiết cho ứng dụng React đã cài đặt ở trên, chúng ta bổ sung thêm các dependence sau:
bootstrap
: framework css dùng để tạo UIaxios
: thư viện dùng để gọi API
npm install bootstrap axios
Sau khi cài đặt xong bootstrap
chúng ta tiến hành import vào trong project để có thể sử dụng
import "bootstrap/dist/css/bootstrap.min.css"
...
main.jsx
Định nghĩa các API
Trong file imageApi
chúng ta định nghĩa các API để thao tác với backend sử dụng axios. Các API này bao gồm:
- Lấy danh sách hình ảnh
- Upload ảnh
- Xóa ảnh
import axios from "axios"
export const ENDPOINT = "http://localhost:8080/api/v1/images"
const imageApi = {
// Lấy danh sách ảnh
getAllImage() {
return axios.get(ENDPOINT)
},
// Upload ảnh
uploadImage(file) {
return axios.post(ENDPOINT, file, {
headers: {
"Content-Type": "form-data"
}
})
},
// Xóa ảnh
deleteImage(id) {
return axios.delete(`${ENDPOINT}/${id}`)
}
}
export default imageApi;
imageApi.js
Lưu ý : Đối với API upload ảnh, vì sử dụng form-data để gửi dữ liệu lên trên server, vì vậy chúng ta cần set Content-Type : “form-data” trong header của request
Hiển thị danh sách hình ảnh ban đầu
Ban đầu chúng ta sẽ gọi API để lấy danh sách các ảnh, để hiển thị dữ liệu ban đầu
Phần gọi API sử dụng useEffect với empty dependence
Các bạn theo dõi đoạn code dưới đây để xem chi tiết
import { useEffect, useState } from "react";
import imageApi, { ENDPOINT } from "./apis/imageApi";
function App() {
// State chứa danh sách ảnh
const [images, setImages] = useState([]);
useEffect(() => {
// Lấy danh sách ảnh ban đầu
const fetchImages = async () => {
try {
// Gọi API lấy danh sách ảnh
const rs = await imageApi.getAllImage();
// Lưu kết quả vào trong state
setImages(rs.data);
} catch (error) {
console.log(error);
}
};
fetchImages();
}, []);
return (
<>
<div className="container py-5">
<div className="mb-4">
<label htmlFor="input" className="btn btn-primary">
Upload image
</label>
<input
type="file"
className="d-none"
id="input"
onChange={handleUploadImage}
/>
</div>
<h2 className="text-center">Image Management</h2>
<div className="py-4">
<div className="row">
{images.map((image) => (
<div key={image.id} className="col-md-3">
<div className="text-center mb-5">
<img
src={`${ENDPOINT}/${image.id}`}
alt={image.name}
/>
<a
href={`${ENDPOINT}/download/${image.id}`}
download={image.name}
className="btn btn-warning mt-2 me-2"
>
Download
</a>
<button
className="btn btn-danger mt-2"
onClick={() =>
handleDeleteImage(image.id)
}
>
Delete
</button>
</div>
</div>
))}
</div>
</div>
</div>
</>
);
}
export default App;
App.jsx
Kết quả ban đầu chúng ta có được như sau
Upload ảnh
Đối với chức năng upload ảnh, chúng ta cần lấy ra được file cần upload trong ô input
Sau đó tạo đối tượng FormData với key là “file” (key này cần chính xác với phần định nghĩa trong API ở backend để có thể đọc dữ liệu) và value chính là file trong ô input
Tiếp đến gọi API và gửi thông tin FormData vừa tạo lên server để xử lý, sau khi thành công, thêm đối tượng mới vào trong state để có thể re-render lại trên giao diện
// Xử lý upload ảnh
const handleUploadImage = async (e) => {
// Lấy ra đối tượng file upload
const file = e.target.files[0];
// Tạo đối tượng formData chứa thông tin file upload
const formData = new FormData();
formData.append("file", file);
try {
// Gọi API upload image
let rs = await imageApi.uploadImage(formData);
// Lưu kết quả sau khi gọi API vào trong state
setImages([rs.data, ...images]);
alert("Upload ảnh thành công");
} catch (error) {
console.log(error);
}
};
App.jsx
Xóa ảnh
Cuối cùng là chức năng xóa ảnh. Trước khi xóa cần người dùng confirm xem có xóa hay không?
Nếu người dùng yêu cầu xóa, lúc này chúng ta sẽ gọi API lên trên server để xóa, khi xóa trên server thành công, tiếp theo sẽ xóa đối tượng image tương ứng trong state để đồng bộ dữ liệu trên giao diện
// Xử lý xóa ảnh
const handleDeleteImage = async (id) => {
// Xác nhận xem người dùng có muốn xóa không
const isConfirm = window.confirm("Bạn có muốn xóa không");
if (!isConfirm) return;
try {
// Gọi API xóa ảnh
await imageApi.deleteImage(id);
// Xóa ảnh trong state
const newImages = images.filter((image) => image.id !== id);
setImages(newImages);
alert("Xóa ảnh thành công");
} catch (error) {
console.log(error);
}
};
App.jsx
Vậy là chúng ta đã hoàn thành ứng dụng “File Management” bên phía FrontEnd. Hi vọng các bạn thấy bài viết này hữu ích và thú vị 😁😁😁
Phần sources code của bài viết này, các bạn có thể tham khảo tại đây: https://github.com/buihien0109/image-management/tree/main/image-management-frontend
Các bạn có thể tham khảo thêm khóa học này nhé:
Bình luận