# 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 (