Tutorial TanStack Query React Native

TanStack Query (formerly known as React Query) is often described as the missing data-fetching library for web applications, but in more technical terms, it makes **fetching, caching, synchronizing and updating server state** in your web applications a breeze.

Installation

npm i @tanstack/react-query

Compatibility

React Query is compatible with React v18+ and works with ReactDOM and React Native.

Recommendations

It is recommended to also use our ESLint Plugin Query to help you catch bugs and inconsistencies while you code. You can install it via:

npm i -D @tanstack/eslint-plugin-query

Example

// App.tsx
import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from '@tanstack/react-query';
import React from 'react';
import { Image, StatusBar, StyleSheet, Text, useColorScheme, View } from 'react-native';
import {
  SafeAreaProvider,
  useSafeAreaInsets,
} from 'react-native-safe-area-context';
function App() {
  const isDarkMode = useColorScheme() === 'dark';
  const queryClient = new QueryClient();
  return (
    <SafeAreaProvider>
      <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
      <QueryClientProvider client={queryClient}>
        <AppContent />
      </QueryClientProvider>
    </SafeAreaProvider>
  );
}
function AppContent() {
  const safeAreaInsets = useSafeAreaInsets();
  const { isPending, error, data, isFetching } = useQuery({
    queryKey: ['repoData'],
    queryFn: async () => {
      const response = await fetch(
        'https://api.github.com/users/DeGsoft',
      )
      return await response.json()
    },
  })
  if (isPending) return (<Text>{'Loading...'}</Text>);
  if (error) return (<Text>{'An error has occurred: ' + error.message}</Text>);
  return (<View style={{ ...styles.container, paddingTop: safeAreaInsets.top }}>
    <Image src={data.avatar_url} style={{ width: 100, height: 100, borderRadius: 50 }} />
    <Text>{data.login}</Text>
    <Text>{data.bio}</Text>
    <Text>👀 {data.followers}</Text>
    <Text>✨ {data.public_repos}</Text>
    <Text>{isFetching ? 'Updating...' : ''}</Text>
  </View>);
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
});
export default App;

DevTools Support

There are several options available for React Native DevTools integration:

1. Native macOS App: A 3rd party app for debugging React Query in any js-based application:
   https://github.com/LovesWorking/rn-better-dev-tools

2. Flipper Plugin: A 3rd party plugin for Flipper users:
   https://github.com/bgaleotti/react-query-native-devtools

3. Reactotron Plugin: A 3rd party plugin for Reactotron users:
   https://github.com/hsndmr/reactotron-react-query

Online status management

React Query already supports auto refetch on reconnect in web browser.
To add this behavior in React Native you have to use React Query `onlineManager` as in the example below:

Install
npm i @react-native-community/netinfo

Example
import NetInfo from '@react-native-community/netinfo'
import { onlineManager } from '@tanstack/react-query'

onlineManager.setEventListener((setOnline) => {
  return NetInfo.addEventListener((state) => {
    setOnline(!!state.isConnected)
  })
})

Or with expo

Install
npx expo install expo-network

Example
import { onlineManager } from '@tanstack/react-query'
import * as Network from 'expo-network'

onlineManager.setEventListener((setOnline) => {
  let initialised = false

  const eventSubscription = Network.addNetworkStateListener((state) => {
    initialised = true
    setOnline(!!state.isConnected)
  })

  Network.getNetworkStateAsync()
    .then((state) => {
      if (!initialised) {
        setOnline(!!state.isConnected)
      }
    })
    .catch(() => {
      // getNetworkStateAsync can reject on some platforms/SDK versions
    })

  return eventSubscription.remove
})

Refetch on App focus

Instead of event listeners on window, React Native provides focus information through the AppState module. You can use the AppState "change" event to trigger an update when the app state changes to "active":

Example
import { useEffect } from 'react'
import { AppState, Platform } from 'react-native'
import type { AppStateStatus } from 'react-native'
import { focusManager } from '@tanstack/react-query'

function onAppStateChange(status: AppStateStatus) {
  if (Platform.OS !== 'web') {
    focusManager.setFocused(status === 'active')
  }
}

useEffect(() => {
  const subscription = AppState.addEventListener('change', onAppStateChange)

  return () => subscription.remove()
}, [])

Refresh on Screen focus

In some situations, you may want to refetch the query when a React Native Screen is focused again.
This custom hook will refetch all active stale queries when the screen is focused again.

First install React Navigation

Example
import React from 'react'
import { useFocusEffect } from '@react-navigation/native'
import { useQueryClient } from '@tanstack/react-query'

export function useRefreshOnFocus() {
  const queryClient = useQueryClient()
  const firstTimeRef = React.useRef(true)

  useFocusEffect(
    React.useCallback(() => {
      if (firstTimeRef.current) {
        firstTimeRef.current = false
        return
      }

      // refetch all stale active queries
      queryClient.refetchQueries({
        queryKey: ['posts'],
        stale: true,
        type: 'active',
      })
    }, [queryClient]),
  )
}

In the above code, the first focus (when the screen is initially mounted) is skipped because useFocusEffect calls our callback on mount in addition to screen focus.

Disable queries on out of focus screens

If you don’t want certain queries to remain “live” while a screen is out of focus, you can use the subscribed prop on useQuery. This prop lets you control whether a query stays subscribed to updates. Combined with React Navigation’s useIsFocused, it allows you to seamlessly unsubscribe from queries when a screen isn’t in focus:

Example
import React from 'react'
import { useIsFocused } from '@react-navigation/native'
import { useQuery } from '@tanstack/react-query'
import { Text } from 'react-native'

function MyComponent() {
  const isFocused = useIsFocused()

  const { dataUpdatedAt } = useQuery({
    queryKey: ['key'],
    queryFn: () => fetch(...),
    subscribed: isFocused,
  })

  return <Text>DataUpdatedAt: {dataUpdatedAt}</Text>
}

When subscribed is false, the query unsubscribes from updates and won’t trigger re-renders or fetch new data for that screen. Once it becomes true again (e.g., when the screen regains focus), the query re-subscribes and stays up to date.

Refresh By User

Create a useRefreshByUser Hook.
This hook wraps TanStack Query’s refetch function to control the refreshing UI state.

Example
export function useRefreshByUser(refetch: () => Promise<unknown>) {
  const [isRefetchingByUser, setIsRefetchingByUser] = useState(false)

  const refetchByUser = useCallback(async () => {
    if (isRefetchingByUser) return;

    setIsRefetchingByUser(true)

    try {
      await refetch()
    } finally {
      setIsRefetchingByUser(false)
    }
  }, [refetch, isRefetchingByUser]);

  return {
    isRefetchingByUser,
    refetchByUser,
  }
}

It ensures:
- the loading spinner appears immediately
- the refresh state always resets
- errors are handled by TanStack Query

Use It in a FlatList
Now connect everything to FlatList's RefreshControl.

Example
import React from 'react';
import { FlatList, Text, View, RefreshControl } from 'react-native';
import { useRepos } from './useRepos';
import { useRefreshByUser } from './useRefreshByUser';

export default function ReposScreen() {
  const { data, isLoading, error, refetch } = useRepos();

  const { isRefetchingByUser, refetchByUser } =
    useRefreshByUser(refetch);

  if (isLoading) {
    return <Text>Loading...</Text>;
  }

  if (error) {
    return <Text>Error loading repositories</Text>;
  }

  return (
    <FlatList
      data={data}
      keyExtractor={(item) => item.id.toString()}
      renderItem={({ item }) => (
        <View>
          <Text>{item.name}</Text>
        </View>
      )}
      refreshControl={
        <RefreshControl
          refreshing={isRefetchingByUser}
          onRefresh={refetchByUser}
        />
      }
    />
  );
}

Why Many React Native Apps Disable refetchOnWindowFocus

Mobile apps behave differently from web apps.
If it is enabled, this can happen:
1 - User opens your app.
2 - App goes to background.
3 - User comes back.
4 - Query auto-refetches silently.

Problems:
Unexpected network calls.
Data refetch while user is scrolling.
Possible UI flicker.

Because of this, many RN apps set:
refetchOnWindowFocus: false

Setting staleTime is the recommended way to avoid excessive refetches
set staleTime to e.g. 2 * 60 * 1000 to make sure data is read from the cache, without triggering any kinds of refetches, for 2 minutes, or until the Query is invalidated manually.

Keep Previous Data

In TanStack Query v5, the keepPreviousData option was removed and its functionality merged into the placeholderData option. The library exports a utility function, also named keepPreviousData, which you can use to achieve the original behavior. 
To use this feature, import the keepPreviousData utility function and provide it to the placeholderData option in your useQuery hook: 
import { useQuery, keepPreviousData } from '@tanstack/react-query';

const { data, isPlaceholderData, isFetching } = useQuery({
  queryKey: ["projects", page],
  queryFn: () => fetchProjects(page),
  placeholderData: keepPreviousData, // Use the exported function
});

This is equivalent to providing an identity function: placeholderData: (previousData) => previousData. 

When you use placeholderData: keepPreviousData (especially for things like pagination or filtering where the queryKey changes):
- Smooth Transitions: The data from the last successful fetch remains available and is displayed while the new data is being requested, preventing the UI from "flashing empty".
- Seamless Swap: When the new data arrives, it seamlessly replaces the previous data in the UI.
- isPlaceholderData Flag: The isPlaceholderData flag is available in the query result object, allowing you to know when the query is currently displaying the old, placeholder data. You can use this flag to add visual cues, such as an overlay or a loading indicator, to inform the user that a background fetch is in progress. 




Comments

Popular posts from this blog

Tutorial Expo React Native Google AdMob

Tutorial VNC Server on Linux Debian Bookworm

Tutorial React Navigation React Native