Quy trình upload file là một trong những tính năng phổ biến nhất trong ứng dụng hiện đại, nhưng việc triển khai tính năng này trên cả React Web và React Native thường dẫn đến việc lặp mã, bất đồng bộ trạng thái và khó bảo trì. Dưới đây là hướng dẫn từng bước để xây dựng một hệ thống upload file gọn nhẹ, có thể tái sử dụng và sẵn sàng mở rộng, kết hợp React Query, Context API và custom hooks. **1. Xác định các trường hợp sử dụng chính** • Upload nhiều file với nhiều loại định dạng (hình ảnh, tài liệu, video). • Theo dõi tiến trình upload, thành công và lỗi. • Hỗ trợ upload trong nền khi người dùng chuyển sang tab khác hoặc đóng ứng dụng. • Tái sử dụng logic xác thực, xử lý lỗi và mutation giữa web và di động. **2. Tạo một hook chung để upload file** ```javascript // useFileUpload.js import { useState, useCallback } from 'react'; import { useQueryClient } from 'react-query'; import { useFileUploadContext } from './FileUploadContext'; const useFileUpload = () => { const [progress, setProgress] = useState(0); const [error, setError] = useState(null); const { setUploads } = useFileUploadContext(); const queryClient = useQueryClient(); const uploadFile = useCallback(async (file, onProgress) => { const formData = new FormData(); formData.append('file', file); try { const response = await fetch('/api/upload', { method: 'POST', body: formData, onUploadProgress: (evt) => { const pct = Math.round((evt.loaded * 100) / evt.total); setProgress(pct); onProgress?.(pct); }, }); if (!response.ok) throw new Error('Upload failed'); const data = await response.json(); setUploads(prev => [...prev, { id: data.id, file, status: 'success' }]); queryClient.invalidateQueries('files'); } catch (err) { setError(err.message); setUploads(prev => [...prev, { id: null, file, status: 'error', message: err.message }]); } }, [setUploads, queryClient]); return { progress, error, uploadFile }; }; ``` **3. Sử dụng React Query để đồng bộ dữ liệu • Định nghĩa một query key như 'files' và sử dụng `useQuery` để lấy danh sách file đã upload. • Sử dụng `useMutation` với `onSuccess`/`onError` để đồng bộ trạng thái upload với backend và cache. **4. Tận dụng Context API cho trạng thái UI toàn cục • Tạo `FileUploadContext` để lưu danh sách các tác vụ upload đang diễn ra, giúp các component con có thể hiển thị thanh tiến trình hoặc danh sách lỗi mà không cần truyền nhiều props. **5. Xây dựng các component có thể tái sử dụng • **UploadZone**: hỗ trợ drag‑and‑drop, hiển thị trạng thái đang chờ, đang upload và kết quả. • **ProgressBar**: lắng nghe `progress` từ hook. • **ErrorToast**: hiển thị lỗi từ context. **6. Mở rộng cho React Native** • Thay thế `fetch` bằng `axios` hoặc `react-native-fs`. • Sử dụng `react-native-async-storage` để lưu danh sách upload khi offline. • Tận dụng cùng một custom hook; các khác biệt về nền tảng được ẩn đi nhờ các abstraction. **7. Các mẹo bảo trì** • Giới hạn số lượng file upload đồng thời để tránh quá tải. • Xác thực định dạng và kích thước file sớm, trước khi gọi API. • Sử dụng `React Query Devtools` để theo dõi trạng thái cache và đồng bộ dữ liệu giữa web và di động. • Viết các test unit cho hook (ví dụ: `jest` + `react-hooks-testing-library`) để đảm bảo logic upload hoạt động chính xác. Bằng cách tập trung logic upload vào một custom hook, sử dụng React Query để đồng bộ dữ liệu và Context API để quản lý trạng thái UI, bạn sẽ có được một quy trình upload file sạch sẽ, có thể tái sử dụng trên cả React Web và React Native. Cách tiếp cận này giúp giảm thiểu việc lặp mã, cải thiện khả năng theo dõi và mang lại trải nghiệm người dùng mượt mà trên tất cả các nền tảng.