TanStack Query в React Native
TanStack Query работает в React Native так же, как в вебе. Но есть нюансы.
Установка
npm install @tanstack/react-query
Провайдер
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000,
gcTime: 5 * 60_000,
retry: 2,
},
},
});
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<NavigationContainer>
<RootNavigator />
</NavigationContainer>
</QueryClientProvider>
);
}
Refetch on focus
В мобильном приложении важно обновлять данные при возврате на экран:
import { focusManager } from '@tanstack/react-query';
import { AppState, Platform } from 'react-native';
import { useEffect } from 'react';
function onAppStateChange(status: AppStateStatus) {
if (Platform.OS !== 'web') {
focusManager.setFocused(status === 'active');
}
}
useEffect(() => {
const sub = AppState.addEventListener('change', onAppStateChange);
return () => sub.remove();
}, []);
Теперь при возврате в приложение данные обновляются автоматически.
Refetch on screen focus
При навигации между экранами:
npm install @react-navigation/native @tanstack/react-query
import { useFocusEffect } from '@react-navigation/native';
import { useQueryClient } from '@tanstack/react-query';
function Screen() {
const queryClient = useQueryClient();
useFocusEffect(
useCallback(() => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
}, [queryClient])
);
}
useQuery
function Posts() {
const { data, isLoading, error, refetch, isRefetching } = useQuery({
queryKey: ['posts'],
queryFn: () => api.get('/posts').then((r) => r.data),
});
if (isLoading) return <ActivityIndicator />;
if (error) return <ErrorState onRetry={refetch} />;
return (
<FlatList
data={data}
renderItem={({ item }) => <PostCard post={item} />}
refreshing={isRefetching}
onRefresh={refetch}
/>
);
}
refreshing + onRefresh — pull to refresh.
useMutation
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (data: PostInput) => api.post('/posts', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
navigation.goBack();
},
});
Оптимистичные обновления
useMutation({
mutationFn: likePost,
onMutate: async (postId) => {
await queryClient.cancelQueries({ queryKey: ['posts'] });
const previous = queryClient.getQueryData(['posts']);
queryClient.setQueryData(['posts'], (old: Post[]) =>
old.map((p) => (p.id === postId ? { ...p, liked: !p.liked } : p))
);
return { previous };
},
onError: (_, __, context) => {
queryClient.setQueryData(['posts'], context?.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
Persist Query Client
Кеш сохраняется между запусками:
npm install @tanstack/query-async-storage-persister @tanstack/react-query-persist-client @react-native-async-storage/async-storage
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
const persister = createAsyncStoragePersister({
storage: AsyncStorage,
key: 'react-query-cache',
});
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister, maxAge: 24 * 60 * 60 * 1000 }}
>
<App />
</PersistQueryClientProvider>
Данные сохраняются между запусками приложения.
Онлайн-статус
npm install @react-native-community/netinfo
import NetInfo from '@react-native-community/netinfo';
import { onlineManager } from '@tanstack/react-query';
onlineManager.setEventListener((setOnline) => {
return NetInfo.addEventListener((state) => {
setOnline(!!state.isConnected);
});
});
Запросы не выполняются офлайн.
Итоги
- TanStack Query работает
focusManagerдля refetch при возвратеuseFocusEffectдля refetch на экране- Persist для кеша между запусками
onlineManagerдля офлайн-режима- Pull to refresh через
refreshing