Khi xây dựng ứng dụng web hoặc di động cần tính năng upload file, bạn thường phải đối mặt với nhiều vấn đề như đồng bộ trạng thái, xử lý lỗi một cách nhất quán và duy trì code dễ bảo trì trên cả React (web) và React Native (di động). Bằng cách kết hợp React Query, Context API và các custom hooks được thiết kế cẩn thận, bạn có thể tạo ra một quy trình upload file mạnh mẽ, sẵn sàng mở rộng và thân thiện với người dùng. **Tại sao sử dụng cách tiếp cận này?** - **React Query** quản lý dữ liệu từ server, tự động thu thập lại dữ liệu sau khi upload, hiển thị trạng thái loading/thành công/lỗi và thậm chí duy trì dữ liệu trong cache ngay cả khi có sự thay đổi mạng. - **Context API** cho phép bạn chia sẻ trạng thái upload (ví dụ: danh sách tệp, tiến trình, lỗi) mà không cần prop drilling, điều này rất quan trọng khi bạn cần sử dụng cùng một logic upload trên các component ở cả hai nền tảng. - **Custom hooks** giúp bạn đóng gói logic (validation, upload tiến trình, xử lý lỗi) thành các đoạn code tái sử dụng, giúp code dễ kiểm thử và dễ bảo trì. **Các bước triển khai** 1. **Tạo một UploadContext** ```js const UploadContext = createContext(null); function UploadProvider({ children }) { const [uploads, setUploads] = useState({}); // key: file.id return ( <UploadContext.Provider value={{ uploads, setUploads }}> {children} </UploadContext.Provider> ); } ``` Context này lưu trữ trạng thái upload hiện tại cho từng tệp, cho phép bất kỳ component nào cũng có thể đọc/ghi vào đó. 2. **Tạo custom hook `useUpload`** Hook này sử dụng React Query để gọi API upload và đồng bộ kết quả với UploadContext. ```js function useUpload() { const { setUploads } = useContext(UploadContext); const mutation = useMutation({ mutationFn: uploadFile, onSuccess: (data, file) => { setUploads(prev => ({ ...prev, [file.id]: { status: 'success', data } })); }, onError: (error, file) => { setUploads(prev => ({ ...prev, [file.id]: { status: 'error', error } })); }, }); return mutation; } ``` 3. **Thêm logic validation và tiến trình** - Xác thực loại tệp, kích thước tối đa, v.v. trước khi upload. - Sử dụng `onUploadProgress` (cho React Native) hoặc các giải pháp dựa trên XHR/fetch để cập nhật tiến trình upload trong context. - Hiển thị một component thông báo trạng thái upload (ví dụ: thanh tiến trình, nút thử lại) bằng cách lắng nghe trạng thái từ context. 4. **Đồng bộ giữa React web và React Native** Vì hook và context được định nghĩa trong một package shared, nên cả hai nền tảng đều có thể sử dụng cùng một logic upload. Điều này giúp đảm bảo trải nghiệm người dùng và xử lý lỗi nhất quán. 5. **Tận dụng tính năng thu thập lại dữ liệu của React Query** Sau khi upload thành công, kích hoạt mutation thành công của React Query để đánh dấu tệp là đã có sẵn trong cache server. Điều này giúp giao diện người dùng có thể hiển thị ngay tệp vừa upload mà không cần tải lại trang. **Lời khuyên thực tế** - **Tách riêng các hàm validation** thành một package riêng để có thể tái sử dụng. - **Hiển thị thông báo lỗi rõ ràng** cho cả web và thiết bị di động; sử dụng cùng một object lỗi từ context. - **Tối ưu hóa hiệu suất** bằng cách sử dụng `useMemo` khi render danh sách tệp và tránh render lại không cần thiết. - **Kiểm thử** các custom hooks với các state giả lập (state mocking) để đảm bảo chúng hoạt động chính xác trên cả hai nền tảng. Bằng cách tuân thủ các mẫu thiết kế này, bạn sẽ có một quy trình upload file dễ bảo trì, hoạt động trên nhiều nền tảng và sẵn sàng cho các tính năng nâng cao trong tương lai (ví dụ: upload nhiều tệp, xem trước tệp, retry logic). Cách tiếp cận này giữ cho code cơ bản sạch sẽ, đồng thời cung cấp cho người dùng phản hồi trực quan đáng tin cậy. **Kết luận** Tận dụng React Query để quản lý trạng thái từ server, Context API để chia sẻ trạng thái một cách thống nhất và custom hooks để đóng gói logic. Kết hợp ba công cụ này lại với nhau, bạn sẽ tạo ra một quy trình upload file hiện đại, có khả năng mở rộng, hoạt động trơn tru trên cả React web và React Native, đồng thời mang lại trải nghiệm người dùng tuyệt vời.