Product CRUD: upload ảnh, preview và progress
Ảnh sản phẩm là một ví dụ rất tốt để nhìn rõ cách Inertia kết nối state UI ở React với validation và storage ở Laravel mà không cần tự xây một REST upload API riêng.Ta đang muốn đạt UX gì?
Ở màn hình Create/Edit:- người dùng chọn ảnh và thấy preview ngay, chưa cần upload;
- chọn ảnh mới sẽ thay preview hiện tại;
- có thể xóa ảnh hiện tại ở Edit;
- submit khóa field để tránh double-submit;
- hiển thị phần trăm upload từ
form.progress; - có thể
cancel()request dài; - lỗi
imagetừ Laravel quay lại đúng field; - nếu update ảnh, request dùng multipart + method spoofing;
- redirect thành công trả về page props mới và thumbnail mới.
1. Schema chỉ lưu path, không lưu binary
public disk.
Sau khi copy sample vào Laravel project, chạy:
2. Form data có cả file và intent xóa ảnh
image là state tạm ở browser. remove_image là intent rõ ràng để backend phân biệt:
null.
3. Preview phải là local UI state
Khi user chọn file, không cần upload ngay chỉ để preview:URL.revokeObjectURL(). Nếu người dùng chọn ảnh nhiều lần mà không cleanup, tab có thể giữ các blob URL cũ lâu hơn cần thiết.
Browser không thể khôi phục giá trị của
<input type="file"> khi back/forward vì lý do bảo mật. Keyed useForm vẫn hữu ích cho các field text/error state, nhưng người dùng phải chọn lại file nếu rời trang trước khi submit.4. File input cập nhật trực tiếp useForm
FormData cho happy path. Inertia có thể chuyển data có file sang multipart request.
Sample vẫn đặt forceFormData: true để intent rõ và để Edit luôn dùng cùng request shape.
5. Upload progress là form lifecycle state
6. Validation vẫn ở Laravel
accept= chỉ là UX hint. Nó không thay thế server validation.
Nếu Laravel reject file:
422 để map error bag.
7. Create: POST multipart bình thường
8. Edit: dùng POST + method spoofing
Multipart vớiPUT/PATCH có thể gây giới hạn ở một số server/framework stack. Pattern an toàn là gửi POST nhưng thêm _method:
PUT.
9. Replace ảnh mà không xóa ảnh cũ quá sớm
Backend sample làm theo thứ tự:catch để giảm orphan file.
Không nên:
10. Delete Product cũng cleanup file
11. Index chỉ nhận image_url, không biết storage path
Server tạo presentation prop:
image_path rồi để React tự đoán public URL/storage convention.
12. Thumbnail ở table
alt="" tránh screen reader đọc lặp.