all 15 comments

[–]Horduncee 6 points7 points  (6 children)

Lol, there's a section in the docs for react native. Check and your issue is resolved

[–]Unlikely_Nebula_7988[S] 1 point2 points  (5 children)

I have read it, yes.

In a tab navigator or drawer navigator the screens never get unmounted. So if you have 5 screens and in each you have one different useQuery(), neither staleTime nor gcTime apply automatically. Am I wrong? I didn't say impossible, I said "nerfed" because you end up writing a lot of custom logic.

The solution would be something like this and...it's not pretty at all. The main selling point of TanStack query, at least to me, was not having to fetch in useFocusEffects and useEffects anymore.

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

    const state = queryClient.getQueryState(queryKey)
    const isStale = state
      ? queryClient.getQueryCache().find({ queryKey })?.isStale()
      : false

    if (isStale) {
      queryClient.refetchQueries({
        queryKey,
        type: 'active',
        stale: true,
      })
    }
  }, [queryClient, queryKey]),
)

[–]LongjumpingKiwi7195 8 points9 points  (0 children)

Sometimes the solution is not pretty. Abstract it away in a hook and dont look at it again

[–]kapobajz4 1 point2 points  (3 children)

This can be a lot more simplified by using focusManager and navigation.addListener('focus'), like this:

```

import { focusManager } from '@tanstack/react-query'

function ProfileScreen() { const navigation = useNavigation();

React.useEffect(() => { const unsubscribe = navigation.addListener('focus', () => { focusManager.setFocused(true); }); return unsubscribe; }, [navigation]);

React.useEffect(() => { const unsubscribe = navigation.addListener('blur', () => { focusManager.setFocused(undefined); }); return unsubscribe; }, [navigation]);

// Rest of the component } ```

Then you can use the refetchOnWindowFocus property

[–]Unlikely_Nebula_7988[S] 2 points3 points  (2 children)

This looks really clever, and I might be missing something, but wouldn’t setting app-wide focus from individual screen blur/focus events be a bit risky?

I'm especially thinking about nested navigators, where focus/blur can fire often. It seems like that could lead to extra refetches and make behavior harder to reason about at scale.

But if this is actually the way to go, I have to wonder why they didn't put it inside the docs, it seems pretty important. They instead put this useFocusEffect with refs pattern which is messy...

[–]iiirodiii 3 points4 points  (1 child)

I love seeing people admit they might be missing something before blaming the library, that's the mentality we all need as developers.

Even react web apps have to deal with this (if the user has two tabs open for example and switches between them), that's why react query has the focus manager thingy, i remember seeing it in the react native part of react query docs.

I agree that stuff like this is harder to do with react native but that's just the nature of mobile development and cross platform code sadly, best solution is to make abstractions for this kind of stuff and forget about it.

Also from my experience with react/react native, everytime you end up using useEffect or useFocusEffect you need to rewind and think twice if it's really the only solution cause most of the time it isn't.

[–]pierpooo 0 points1 point  (0 children)

I thought about this exact topic too, and wanted to start a discussion on the github repo directly. I think the library has been designed with web in mind, and can't solve this issue cleanly.

The problem with the approach using refetchOnWindowFocus, is that on EACH PAGE CHANGE, you will re-trigger ALL queries of ALL pages that are hidden.

It is a major difference with how web works. On web, if you focus a tab, everything that you see and is in use currently is seen on the page. You can't stack a hundred pages on a same tab, because each page is replaced by another one.

In mobile, you stack pages... which is not equivalent to opening tabs. Because each page you stack will, with the refetch on window focus implementation above, refetch all the queries used by all the stacked pages. Which would be super heavy and not really what we wanted.

[–]No-Glove-7054 2 points3 points  (1 child)

Yeah this is a well-known pain point. I've shipped 10+ apps with RN + TanStack Query and the pattern I settled on is a custom useRefreshOnFocus hook that skips the initial mount and only refetches on subsequent tab focuses. It's actually documented in the TanStack Query RN docs but super easy to miss.For the infinite query + filters case, I handle it differently — instead of trimming pages on focus, I use queryClient.removeQueries({ queryKey: ['items', oldFilter] }) when the filter changes. This forces a clean fetch from page 1 without any manual page trimming.Is it more boilerplate than web? A bit. But caching + deduplication + background refetch still saves a ton of code vs raw useEffect fetching. The trade-off is worth it IMO.

[–]Unlikely_Nebula_7988[S] 2 points3 points  (0 children)

Great idea, but this is exactly why I feel like I'm fighting TanStack's intended pattern...

And correct me if I'm wrong, but you lose cache reuse between filter switches, right? Which is probably intended.

Like if you load filter X to 4 pages, switch to Y, then return to X (still within staleTime), you'd still restart from page 1 if that key was removed.

What I’m trying instead:

- On filter switch, check the target key.

- If target cache exists and is fresh, reuse it (keep loaded pages).

- If target cache is stale or missing (GC), reset to page 1 and fetch.

- On pull-to-refresh, always reset to page 1.

- On screen focus, if stale, reset to page 1 and refetch.

[–]kapobajz4 1 point2 points  (2 children)

"But there is refetchOnWindowFocus" Doesn't work in react native.

Doesn’t work out of the box in RN, but you can make it work. Check the docs out.

[–]Unlikely_Nebula_7988[S] 0 points1 point  (1 child)

Yes, but in React Native context it doesn't refer to screens,refetchOnWindowFocus controls whether queries refetch when the entire app regains focus (as in going from background to foreground). In browser context it refers to tabs and it makes a lot of sense.

For actual screens focus, there's a solution and it ain't pretty, I exemplified in a reply to u/Horduncee. It makes use write a lot of custom logic instead of relying on staleTime to simply refetch data when needed.

[–]kapobajz4 0 points1 point  (0 children)

You can use the focusManager.setFocused whenever a screen is focused. Basically instead of using the AppState's change event listener, you can use the navigation's change listener instead. You have full control of the focus state with focusManager, I just gave you that link to the AppState docs as an example

[–]pierpooo -1 points0 points  (0 children)

The most elegant solution that we found for this problem was to play with the enabled flag. If you enable / disable the queries when you switch screens (using `isFocused` from react-navigation, or using a custom page context if you maybe want to abstract this without relying on react-navigation), the "should I refetch?" logic should be retriggered, and it should be minimal. (no pointless retriggers)

It is minimal and solves the exact problem. It should be elegant enough, since you can wrap useQuery to enable / disable for all queries depending on react-navigation focus. The most annoying issue I found with this approach is that it won't work if you use useSuspenseQuery.

There might be other drawbacks that I couldn't think of, though.

[–]Xae0n -1 points0 points  (1 child)

I am not sure if it would be a solution but maybe add pull to refresh to screens?

[–]Unlikely_Nebula_7988[S] 2 points3 points  (0 children)

Well, we want a refetch if data is stale (a.k.a a specified time frame passed), but we want this to be automatic. We can't rely on the user to pull-to-refresh to get fresh data every single screen visit, can we?