When building a React Native app, you often need to fetch data from external APIs or save data on a server. Using only
fetch means repeatedly implementing loading states, error handling, caching, and other common patterns yourself.
TanStack Query
makes data fetching much simpler and more reliable. This post walks through installing and using TanStack Query in React Native (Expo), starting with the basics and working through examples.
What Is TanStack Query? ๐
TanStack Query is a state management library that helps efficiently manage data fetched from servers. Previously called React Query, it has evolved into a general-purpose tool supporting multiple platforms.
It is useful in React Native when you want to:
Cache server data and avoid duplicate requests.
Automatically refetch data based on network connectivity or app focus.
Automatically handle error and loading states.
Installation and Expo Setup ๐
Install TanStack Query in a React Native (Expo) project as follows:
npx expo install @tanstack/react-query
npm i -D @tanstack/eslint-plugin-query
Set Up QueryClientProvider ๐
After installation, create a QueryClient and wrap your app's top-level component in QueryClientProvider.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' ;
const queryClient = new QueryClient ();
export default function App () {
return (
< QueryClientProvider client ={ queryClient } >
{ /* ์ฑ ์ปดํฌ๋ํธ๋ค */ }
</ QueryClientProvider >
);
}
This makes TanStack Query features available throughout the app.
Basic useQuery Usage ๐
Let's start with the most basic example: fetching JSONPlaceholder's users API (/users) and displaying the results as a list.
import { useQuery } from '@tanstack/react-query' ;
import axios from 'axios' ;
import { FlatList, Text, View } from 'react-native' ;
const fetchUsers = async () => { // ์ ๋ณด๋ฅผ fetchํ๋ ํจ์
const res = await axios. get ( 'https://jsonplaceholder.typicode.com/users' );
return res.data;
};
export default function UserList () {
const { data , isLoading , isError , error } = useQuery ({
queryKey: [ 'users' ], // ์ฟผ๋ฆฌ์ ๊ณ ์ ์๋ณ์
queryFn: fetchUsers, // ์ ๋ณด๋ฅผ fetchํ๋ ํจ์
});
if (isLoading) return < Text >๋ถ๋ฌ์ค๋ ์ค...</ Text >;
if (isError) return < Text >์ค๋ฅ ๋ฐ์: { (error as Error ).message } </ Text >;
return (
< FlatList
data ={ data }
keyExtractor ={ ( item ) => item.id. toString () }
renderItem ={ ({ item }) => < Text > { item.name } </ Text > }
/>
);
}
The Importance of Query Keys (queryKey) ๐
The queryKey plays a crucial role in identifying requests and applying caching. It is written as an array and can vary according to specific parameters.
useQuery ({
queryKey: [ 'user' , userId],
queryFn : () => fetchUser (userId),
});
Here, different userId values identify different queries, each with its own cache.
Refetch Based on App Focus ๐
Use AppState and focusManager to automatically rerun queries when the app gains focus:
import { useEffect } from 'react' ;
import { AppState, Platform } from 'react-native' ;
import { focusManager } from '@tanstack/react-query' ;
function onAppStateChange ( status ) {
if (Platform. OS !== 'web' ) {
focusManager. setFocused (status === 'active' );
}
}
useEffect (() => {
const subscription = AppState. addEventListener ( 'change' , onAppStateChange);
return () => subscription. remove ();
}, []);
Refetch on Screen Focus with React Navigation ๐
With React Navigation, you can use useFocusEffect to refresh data when entering a screen:
import { useFocusEffect } from '@react-navigation/native' ;
import { useCallback } from 'react' ;
export function useRefreshOnFocus ( refetch ) {
useFocusEffect (
useCallback (() => {
refetch ();
}, [])
);
}
Use useMutation to create or update data on the server. Here is an example of adding a post:
import { useMutation, useQueryClient } from '@tanstack/react-query' ;
import axios from 'axios' ;
const createPost = async ( newPost ) => {
const res = await axios. post ( 'https://jsonplaceholder.typicode.com/posts' , newPost);
return res.data;
};
export function NewPostButton () {
const queryClient = useQueryClient (); // ์ฟผ๋ฆฌ ํด๋ผ์ด์ธํธ ์ธ์คํด์ค
const mutation = useMutation ({
mutationFn: createPost, // ๋ฎคํ
์ด์
ํจ์
onSuccess : () => {
queryClient. invalidateQueries ({ queryKey: [ 'posts' ] }); // ์ฟผ๋ฆฌ ๋ฐ์ดํฐ ๋ฌดํจํ
},
});
return (
< Button
title = "์ ๊ธ ์์ฑ"
onPress ={ () => mutation. mutate ({ title: '์ ๋ชฉ' , body: '๋ด์ฉ' }) } // ๋ฎคํ
์ด์
์คํ
/>
);
}
Using Devtools in React Native ๐
Unlike on the web, Devtools cannot be used directly in React Native. You can inspect query state with tools such as:
Install the plugin with the following command.
npx expo install @dev-plugins/react-query
Configure it as follows to use it.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' ;
import { useReactQueryDevTools } from '@dev-plugins/react-query' ;
const queryClient = new QueryClient ({});
export default function App () {
useReactQueryDevTools (queryClient); // ๋๋ฒ๊น
๋๊ตฌ ์ด๊ธฐํ
return (
< QueryClientProvider client ={ queryClient } >
{ /* ... */ }
</ QueryClientProvider >
);
}
Now press Shift + M in the terminal running your app and select Open @dev-plugins/react-query to open the debugging tools in a web browser.
TanStack Query makes data fetching, caching, error handling, and state management simple and efficient in React Native (Expo). Automatic data synchronization based on network connectivity and app focus is particularly useful on mobile.
I hope these examples have helped you move from the basics to practical usage.