diff --git a/ReactQuery_Axios.md b/ReactQuery_Axios.md new file mode 100644 index 0000000..8d55b27 --- /dev/null +++ b/ReactQuery_Axios.md @@ -0,0 +1,1494 @@ +# React Query (TanStack Query) + Axios cho React Native với Expo + +## Mục Lục + +1. [React Query là gì?](#react-query-là-gì) +2. [Tư duy Server-State vs Client-State](#tư-duy-server-state-vs-client-state) +3. [Chiến thuật Caching trong React Query](#chiến-thuật-caching-trong-react-query) +4. [Các Hooks quan trọng trong React Query](#các-hooks-quan-trọng-trong-react-query) +5. [Kết hợp React Query + Axios](#kết-hợp-react-query--axios) +6. [Ví dụ đầy đủ cho React Native + Expo](#ví dụ-đầy-đủ-cho-react-native--expo) +7. [Tối ưu Performance cho React Native](#tối-optim-performance-cho-react-native) + +--- + +## React Query là gì? + +React Query (nay là TanStack Query) là một library giúp quản lý **server-state** trong React applications. Nó giải quyết các vấn đề thường gặp khi làm việc với dữ liệu từ server: + +- **Fetching data**: Lấy dữ liệu từ API +- **Caching**: Lưu trữ dữ liệu để tránh gọi API lại không cần thiết +- **Synchronizing**: Đồng bộ dữ liệu khi có thay đổi từ server +- **Updating**: Cập nhật dữ liệu sau khi mutation +- **Managing state**: Quản lý các trạng thái (loading, error, success) + +### Tại sao nên dùng React Query? + +```javascript +// ❌ Không dùng React Query +function UserProfile() { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + fetchUser() + .then(setUser) + .catch(setError) + .finally(() => setLoading(false)); + }, []); + + if (loading) return ; + if (error) return Error: {error.message}; + return {user.name}; +} + +// ✅ Dùng React Query +function UserProfile() { + const { data: user, isLoading, error } = useQuery({ + queryKey: ['user'], + queryFn: fetchUser + }); + + if (isLoading) return ; + if (error) return Error: {error.message}; + return {user.name}; +} +``` + +--- + +## Tư duy Server-State vs Client-State + +### Server-State +- **Nguồn gốc**: Đến từ server (API, database) +- **Đặc điểm**: Có thể bị thay đổi bởi user khác, cần đồng bộ +- **Ví dụ**: Danh sách sản phẩm, thông tin user, dữ liệu real-time + +### Client-State +- **Nguồn gốc**: Tạo ra trong ứng dụng +- **Đặc điểm**: Chỉ tồn tại trong client, không cần đồng bộ +- **Ví dụ**: Theme dark/light, form input state, UI state + +```javascript +function App() { + // ❌ Sai: Đẩy server-state vào client-state + const [products, setProducts] = useState([]); + + // ✅ Đúng: Client-state cho UI, server-state cho data + const [isModalOpen, setIsModalOpen] = useState(false); // Client-state + const { data: products } = useQuery({ queryKey: ['products'], queryFn: fetchProducts }); // Server-state +} +``` + +--- + +## Chiến thuật Caching trong React Query + +### Stale-While-Revalidate (SWR) + +```javascript +useQuery({ + queryKey: ['posts'], + queryFn: fetchPosts, + staleTime: 1000 * 60, // 1 phút + cacheTime: 1000 * 60 * 5, // 5 phút +}); +``` + +**Luồng hoạt động:** + +1. **First request**: Gọi API → Lưu vào cache +2. **Second request (trong 1 phút)**: Trả data từ cache (instant) ✨ +3. **After 1 minute**: Trả data từ cache + background refetch 🔄 +4. **After 5 minutes**: Xóa khỏi cache 🗑️ + +### Các thời gian quan trọng + +```javascript +{ + staleTime: 0, // Luôn coi data là cũ (mỗi request đều gọi API) + staleTime: Infinity, // Luôn coi data là mới (không bao giờ refetch) + + cacheTime: 1000 * 60 * 5, // Giữ cache 5 phút sau khi unused + + refetchOnWindowFocus: true, // Refetch khi app focus (React Native) + refetchOnReconnect: true, // Refetch khi reconnect internet + refetchOnMount: false, // Không refetch khi component mount +} +``` + +--- + +## Các Hooks quan trọng trong React Query + +### 1. useQuery + +Dùng để fetch và cache data read-only. + +```javascript +// Cú pháp cơ bản +const { + data, + isLoading, + error, + refetch, + isSuccess, + isError +} = useQuery({ + queryKey: ['unique-key'], + queryFn: () => axios.get('/api/data'), + options: { /* options */ } +}); + +// Ví dụ thực tế +function UserList() { + const { + data: users = [], + isLoading, + error + } = useQuery({ + queryKey: ['users'], + queryFn: async () => { + const response = await axios.get('/api/users'); + return response.data; + }, + staleTime: 1000 * 60 * 5, // 5 phút + enabled: true, // Có enabled/disabled query + }); + + if (isLoading) return ; + if (error) return Lỗi: {error.message}; + + return ( + {item.name}} + keyExtractor={item => item.id} + /> + ); +} +``` + +**Options quan trọng:** + +```javascript +{ + // Dependencies - Query sẽ refetch khi dependencies thay đổi + queryKey: ['users', { page: 1, search: 'john' }], + + // Conditional fetching + enabled: userId !== null, + + // Retry config + retry: 3, + retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000), + + // Select transformation + select: (data) => data.users.map(user => ({ ...user, fullName: `${user.firstName} ${user.lastName}` })), +} +``` + +### 2. useMutation + +Dùng cho các tác vụ thay đổi data (POST, PUT, DELETE). + +```javascript +// Cú pháp cơ bản +const mutation = useMutation({ + mutationFn: (variables) => axios.post('/api/data', variables), + onSuccess: (data, variables, context) => { /* success */ }, + onError: (error, variables, context) => { /* error */ }, + onSettled: (data, error, variables, context) => { /* luôn chạy */ }, +}); + +// Ví dụ thực tế +function CreateUserForm() { + const [name, setName] = useState(''); + + const createUserMutation = useMutation({ + mutationFn: async (userData) => { + const response = await axios.post('/api/users', userData); + return response.data; + }, + onSuccess: (newUser) => { + Alert.alert('Thành công', `Đã tạo user ${newUser.name}`); + queryClient.invalidateQueries({ queryKey: ['users'] }); // Refetch danh sách users + }, + onError: (error) => { + Alert.alert('Lỗi', error.response?.data?.message || 'Không thể tạo user'); + }, + onMutate: async (newUser) => { + // Cancel ongoing queries + await queryClient.cancelQueries({ queryKey: ['users'] }); + + // Snapshot previous value + const previousUsers = queryClient.getQueryData(['users']); + + // Optimistic update + queryClient.setQueryData(['users'], old => [...old, { id: 'temp', ...newUser }]); + + return { previousUsers }; + }, + onError: (err, newUser, context) => { + // Rollback on error + queryClient.setQueryData(['users'], context.previousUsers); + }, + }); + + const handleSubmit = () => { + createUserMutation.mutate({ name }); + }; + + return ( + + +