Skip to content
\n

However, there's a case in which the query function gets called, but the query data remains stale That happens when the user isn't on the chat screen (query is inactive).

\n

Below you can see how my MessagesQuery looks like:

\n
// This is the maximum number of pages to keep in the infinite query\nconst maxPagesToKeep = 3;\n\nexport namespace MessagesQuery {\n    export const keys = {\n        all: [\"communication/useMessages\"] as const,\n        channel: (channelId: string, messageId = 0) =>\n            [...keys.all, { channelId }, { messageId }] as const,\n    };\n\n    export const invalidate = (\n        channelId?: string,\n        filters?: InvalidateQueryFilters<readonly Message[]>,\n    ) => {\n        if (!channelId) {\n            return queryClient.invalidateQueries(keys.all);\n        }\n\n        // Cut off redundant pages from the infinite query\n        // This way we won't have to wait for all the pages to refetch when we invalidate the query\n        // Thus the user will see all UI updates faster\n        InfiniteQueryService.cutOffRedundantPagesFromInfiniteQuery(\n            () => MessagesQuery.getData(channelId),\n            (data) => MessagesQuery.setData(keys.channel(channelId), data),\n            maxPagesToKeep,\n        );\n\n        return queryClient.invalidateQueries({\n            queryKey: keys.channel(channelId),\n            ...filters,\n        });\n    };\n\n  \n\n    type QueryData = InfiniteData<readonly Message[]>;\n\n    export const getData = (channelId: string, messageId?: number) => {\n        return queryClient.getQueryData<QueryData>(\n            keys.channel(channelId, messageId),\n        );\n    };\n\n    export const setData = (key: readonly unknown[], data: QueryData) => {\n        return queryClient.setQueryData<QueryData>(key, data);\n    };\n\n    export const pageLimit = 15;\n\n    export const useQuery = (\n        channelId: string,\n        messageId?: number,\n        options: UseInfiniteQueryOptions<readonly Message[]> = = {\n            enabled: true,\n        }\n    ) => {\n        const isDisconnected = useIsDisconnected();\n\n        const queryInfo = useInfiniteQuery<readonly Message[]>({\n            ...options,\n            queryKey: keys.channel(channelId, messageId),\n            enabled:\n                !isDisconnected && options?.enabled && Boolean(api.getToken()),\n            getNextPageParam: (lastPage): number | undefined => {\n                if (!lastPage) return undefined;\n                return last(lastPage)?.id;\n            },\n            queryFn: async ({ pageParam: lastMessageId }) => {\n                const messages = await api.communication.getMessages(\n                    channelId,\n                    pageLimit,\n                    lastMessageId,\n                    messageId,\n                );\n\n                // After messages have been fetched, check if channels need to be invalidated\n                // Use case: When a user receives a notification, channels need to refetch after the messages from a channel have been fetched,\n                // since the channel.unreadCount will be updated only after the messages have been fetched\n                // otherwise the unread count will be incorrect\n                const notificationFlags = useNotificationFlagsStore.getState();\n                if (notificationFlags.value.invalidateChannels) {\n                    ChannelsQuery.invalidate();\n                    notificationFlags.update({ invalidateChannels: false });\n                }\n\n                const orderedMessages = [];\n\n                for (let i = 0; i < messages.length; i++) {\n                    const firstMessage = messages[i];\n                    const secondMessage = messages[i + 1];\n\n                    if (!secondMessage) {\n                        orderedMessages.push(firstMessage);\n                        break;\n                    } else if (\n                        // This is necessary since the FlatList is inverted and system messages are displayed after the regular messages\n                        firstMessage.createdAt === secondMessage.createdAt &&\n                        firstMessage.isSystemMessage &&\n                        !secondMessage.isSystemMessage\n                    ) {\n                        orderedMessages.push(secondMessage);\n                        orderedMessages.push(firstMessage);\n                        i++;\n                    } else {\n                        orderedMessages.push(firstMessage);\n                    }\n                }\n\n                return orderedMessages;\n            },\n        });\n\n    \n        return {\n            ...queryInfo,\n            data: {\n                ...queryInfo.data,\n                pages: queryInfo.data?.pages.flat() ?? [],\n            },\n        };\n    };\n}
\n

When I add a log to display the newest message in the queryFn I can see that the new data is fetched, while the same log on dataFlattened returns the old message

\n

queryClient configuration:

\n
export const queryClient = new QueryClient({\n    defaultOptions: {\n        queries: {\n            staleTime: INTERVALS.ms1h * 5,\n            cacheTime: INTERVALS.ms1h * 5,\n            keepPreviousData: true,\n        },\n    },\n});\n\nexport const persister = createAsyncStoragePersister({\n    storage: AsyncStorage,\n});
\n

From what I could understand, it looks like the pageParams issue occurs because the first page doesn't get updated.
\nThe first-page param is null , and even though the data for that page has changed, it doesn't invalidate other page params and thus keeps the cache the same 🤔

","upvoteCount":2,"answerCount":1,"acceptedAnswer":{"@type":"Answer","text":"

I was able to solve the issue. It was because next page was fetching while the whole query was refeteched. Here's the whole breakdown

\n
    \n
  1. Messages are rendered in a FlatList
  2. \n
  3. There was an onEndReached callback that calls messagesQuery.fetchNextPage
  4. \n
  5. I've added an optimization that displays up to 10 first messages while the screen is transitioning. After the transition has ended other messages are displayed.
  6. \n
  7. Since all 10 messages are rendered on the screen, the onEndReachedCb was called during transition, every time the screen was opened. This resulted in many redundant pages being fetched, even though the user didn't scroll nearly to the end
  8. \n
  9. When the messages query was invalidated, and the user opened the screen, a new page was fetched, while all other pages were refetched. I'm not sure what would happen in this case but it resulted in inconsistent query data 😅 (maybe the TanstackQuery wizard himself @TkDodo could explain what happenes in TanStack Query in this case)
  10. \n
\n

I solved the issue by adding a check that would only attach onEndReachedCb after the transition has ended:

\n
onEndReached={hasTransitionEnded ? fetchMoreMessages : null}
","upvoteCount":3,"url":"https://github.com/TanStack/query/discussions/6709#discussioncomment-8142957"}}}

[useInfiniteQuery] Query data doesn't update even though queryFn has been called #6709

Answered by dr-zr
dr-zr asked this question in Q&A
Discussion options

You must be logged in to vote

I was able to solve the issue. It was because next page was fetching while the whole query was refeteched. Here's the whole breakdown

  1. Messages are rendered in a FlatList
  2. There was an onEndReached callback that calls messagesQuery.fetchNextPage
  3. I've added an optimization that displays up to 10 first messages while the screen is transitioning. After the transition has ended other messages are displayed.
  4. Since all 10 messages are rendered on the screen, the onEndReachedCb was called during transition, every time the screen was opened. This resulted in many redundant pages being fetched, even though the user didn't scroll nearly to the end
  5. When the messages query was invalidated, and the use…

Replies: 1 comment 2 replies

Comment options

You must be logged in to vote
2 replies
@mkeyy0
Comment options

@RomaricMourgues
Comment options

Answer selected by dr-zr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet
3 participants