Your React Native App Reopens With Old Data. Here Is The Fix.
You open a mobile app, check a list, then switch to something else. An hour later you come back. The screen is still there. So is the old data.
Pull-to-refresh works, but the app should not need a reminder every time you return. If your React Native app already uses TanStack Query, there is a small piece of wiring worth checking before you add another timer.
The Problem Developers Keep Running Into
Coming back to an app is not necessarily a new component mount. A fetch tied only to the first render can leave the same list on screen. React Native exposes foreground changes through AppState; the initial state can also be unknown on the legacy architecture.
Connect App State And Connectivity Once
This example targets React Native on iOS and Android with TanStack Query v5 and @react-native-community/netinfo. Install compatible versions through your project’s normal setup. For Expo, use npx expo install @react-native-community/netinfo. For a bare React Native app, follow the package’s native installation instructions and rebuild where required.
Save this as nativeQueryEvents.js. It connects native events to Query’s focus and online managers. It does not manually refetch every query.
import { AppState, Platform } from 'react-native';
import NetInfo from '@react-native-community/netinfo';
import { focusManager, onlineManager } from '@tanstack/react-query';
// Mount once at the application root, not once per screen.
export function subscribeToNativeQueryEvents() {
if (Platform.OS === 'web') return;
let mounted = true;
const stopNetwork = NetInfo.addEventListener(state => {
if (!mounted) return;
// Unknown is not the same as offline. Let the request try.
onlineManager.setOnline(
state.isConnected !== false &&
state.isInternetReachable !== false
);
});
function updateFocus(state) {
if (!mounted || state == null) return;
focusManager.setFocused(state === 'active');
if (state === 'active') {
// Refresh also notifies the NetInfo subscriber above.
void NetInfo.refresh().catch(() => {
// Keep the last connectivity state if the check fails.
});
}
}
const subscription = AppState.addEventListener('change', updateFocus);
updateFocus(AppState.currentState);
return () => {
mounted = false;
subscription.remove();
stopNetwork();
};
}
Only an explicit false blocks connectivity here. NetInfo can report null while reachability is unknown; this snippet deliberately allows a request in that case. A positive network signal still does not prove your API is healthy.
The foreground refresh matters because network changes while an iOS app is backgrounded can leave its connectivity information out of date. NetInfo.refresh() updates subscribers too. If that check rejects, this example retains the last state rather than guessing that the device is offline.
Put It Above Your Screens
Save this next to the first file as AppProviders.jsx, then wrap your existing navigation tree with <AppProviders>. If you already have a QueryClientProvider, add the effect there and merge these options into its client. Do not introduce a second client or install the listeners on every screen.
import { useEffect, useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { subscribeToNativeQueryEvents } from './nativeQueryEvents';
export function AppProviders({ children }) {
const [client] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000,
refetchOnWindowFocus: true,
refetchOnReconnect: true,
networkMode: 'online',
},
},
}));
useEffect(subscribeToNativeQueryEvents, []);
return (
<QueryClientProvider client={client}>
{children}
</QueryClientProvider>
);
}
The state initializer keeps the client stable across renders. Your existing useQuery calls still supply the query keys, authenticated requests and server response handling. These two files add event handling, not a backend.
Why Returning Does Not Always Send A Request
The 60-second staleTime is an example policy. Return after ten seconds and fresh cached data can stay as it is. Return after two minutes and an enabled, observed stale query can refetch. The value is a freshness window, not a timer that polls every minute. Query-specific options can override these defaults.
With networkMode: 'online', a query that needs the network can pause while the manager reports offline and resume after reconnecting. Your UI should distinguish a paused request from an active fetch. Cached data is useful during either state; replacing it with a full-screen spinner on every return is usually unnecessary.
Check It On A Phone
- Load a query, leave briefly and return within your freshness window. Check that it does not refetch just because the app became active.
- Leave longer than the window, change the server data elsewhere, then return. Check that the list updates.
- Go offline, trigger a query, then reconnect. Check the paused state and whether it resumes.
- Background the app, switch networks, then reopen it. Include a real iPhone in this check.
- Unmount and remount the root during development. Check for duplicate subscriptions.
The snippet was checked with mocked native events and real TanStack Query core observers: 12 checks covered focus, freshness, reconnect, paused requests, unknown connectivity, cleanup and a failed network refresh. The JSX also compiled. Those checks do not replace an Android or iOS device test.
Keep The Scope Clear
This refreshes data when the app is running and native events arrive. It does not keep JavaScript alive after the operating system suspends or terminates the app. It also does not add persistent storage, queue offline writes or handle navigation-screen focus. Start here for stale data on return; treat those as separate requirements.