You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a long-standing issue when using infinite queries with hydration, which means both SSR and persistence are affected. See this old issue from 2020:
The root of this problem stems from the fact that we leverage undefined as the default pageParam. When you fetch the first page, you get undefined passed into the queryFn as pageParam. This is convenient because you can now assign a default value for the first page, as shown in the docs:
pageParam = 0 works because we give you undefined for the first page. However, the pageParms are also stored in the resulting data (so that we can use it for refetches), which means it's stored as:
undefined is problematic when serializing as json, which dehydration does.
For SSR, nextJs cannot dehydrate undefined and will just give you an error.
For persistence, it means we'll actually store pageParams: [null, page2PageParam]. This "works", but as soon as you restore from persistence, you will no longer get undefined passed into the queryFn - you'll get null instead. And default value assignments do not work with null, meaning a refetch of the first page will fail.
Infinite Queries have a feature where you can manually fetch a page with a different pageParam than the one you get from getNextPageParam or getPreviousPageParam. If you do that however, refetching will not work anymore for that page as shown in the issue, because we don't "remember" that this page was invoked manually.
Proposal
To fix issue one, the proposal is to move away from storing and passing undefined and instead requiring a new option defaultPageParam passed to useInfiniteQuery. The above example would become:
Not only will this side-step the issue (because we don't store undefined at all), It might also close a loophole in the typings, as pageParam is currently defined as any. We could add another generic TPageParam, which should be inferred from the defaultPageParam - and it should also be what getNextPageParam returns (TPageParam | undefined).
To fix issue two, the proposal would be to remove the possibility to pass manual pageParam to fetchNextPage. For example, this example from the docs wouldn't work anymore:
Given that the feature is currently broken it is doubtful that many people are using it or even know that it exists. If you want something like that, paginated queries might be the better fit. Further, the implementation currently checks for the avilability of a getNextPageParam function to decide if we are manually fetching, so it doesn't work if you're only using getPreviousPageParam:
With this removal, we could also make either getNextPageParam or getPreviousPageParam required on type level.
This removal brought up the question of why we even need to store pageParams at all, because we're not using it for refetches. The only thing that comes to mind as to why we need to store them is so that bi-directional refetches basically know where to start.
Consider the following data (made with useInfiniteQuery({ defaultPageParam: 0 })):
if we now do a refetch, we must know that the first refetch happens with the pageParam -10 and not with the defaultPageParam of 0.
Alternatives
No alternatives considered for issue number 1
For issue number 2, we could decide to not store pageParams at all, but instead only store the pageParam of the first page somewhere else, on the query state.
This would mean we could get rid of the nested structure of { pages, pageParams } and essentially allow useInfiniteQuery to just return:
[page1Data, page2Data, page3Data]
basically being Array<TQueryFnData>. I don't know how feasibable this would be to implement.
Additions - TypeScript issues
There are two TypeScript issues with Infinite Queries that would be good to fix in v5:
select needs to return the same structure (TInfiniteData) and is thus way less usable. It works at runtime if you transform to something else, but the types don't allwo it.
this goes together with 1: onSuccess does get passed data transformed by select at runtime, but not on type level.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Motivation
There are already two issues on the v5 roadmap regarding infinite queries that we cannot fix in v4:
This is a long-standing issue when using infinite queries with hydration, which means both
SSR
andpersistence
are affected. See this old issue from 2020:The root of this problem stems from the fact that we leverage
undefined
as the default pageParam. When you fetch the first page, you getundefined
passed into thequeryFn
aspageParam
. This is convenient because you can now assign a default value for the first page, as shown in the docs:pageParam = 0
works because we give youundefined
for the first page. However, the pageParms are also stored in the resulting data (so that we can use it for refetches), which means it's stored as:undefined
is problematic when serializing as json, which dehydration does.For SSR, nextJs cannot dehydrate
undefined
and will just give you an error.For persistence, it means we'll actually store
pageParams: [null, page2PageParam]
. This "works", but as soon as you restore from persistence, you will no longer getundefined
passed into thequeryFn
- you'll getnull
instead. And default value assignments do not work withnull
, meaning a refetch of the first page will fail.Infinite Queries: wrong auto refetching with manual pageParams #4464
Infinite Queries have a feature where you can manually fetch a page with a different pageParam than the one you get from
getNextPageParam
orgetPreviousPageParam
. If you do that however, refetching will not work anymore for that page as shown in the issue, because we don't "remember" that this page was invoked manually.Proposal
To fix issue one, the proposal is to move away from storing and passing
undefined
and instead requiring a new optiondefaultPageParam
passed touseInfiniteQuery
. The above example would become:Not only will this side-step the issue (because we don't store
undefined
at all), It might also close a loophole in the typings, aspageParam
is currently defined asany
. We could add another genericTPageParam
, which should be inferred from thedefaultPageParam
- and it should also be whatgetNextPageParam
returns (TPageParam | undefined
).To fix issue two, the proposal would be to remove the possibility to pass manual
pageParam
tofetchNextPage
. For example, this example from the docs wouldn't work anymore:Given that the feature is currently broken it is doubtful that many people are using it or even know that it exists. If you want something like that, paginated queries might be the better fit. Further, the implementation currently checks for the avilability of a
getNextPageParam
function to decide if we are manually fetching, so it doesn't work if you're only usinggetPreviousPageParam
:query/packages/query-core/src/infiniteQueryBehavior.ts
Line 123 in 8c373d2
With this removal, we could also make either
getNextPageParam
orgetPreviousPageParam
required on type level.This removal brought up the question of why we even need to store
pageParams
at all, because we're not using it for refetches. The only thing that comes to mind as to why we need to store them is so that bi-directional refetches basically know where to start.Consider the following data (made with
useInfiniteQuery({ defaultPageParam: 0 })
):and now we call
fetchPreviousPage()
this will give us:if we now do a refetch, we must know that the first refetch happens with the
pageParam
-10 and not with thedefaultPageParam
of0
.Alternatives
No alternatives considered for issue number 1
For issue number 2, we could decide to not store
pageParams
at all, but instead only store thepageParam
of the first page somewhere else, on the query state.This would mean we could get rid of the nested structure of
{ pages, pageParams }
and essentially allowuseInfiniteQuery
to just return:basically being
Array<TQueryFnData>
. I don't know how feasibable this would be to implement.Additions - TypeScript issues
There are two TypeScript issues with Infinite Queries that would be good to fix in v5:
select
needs to return the same structure (TInfiniteData
) and is thus way less usable. It works at runtime if you transform to something else, but the types don't allwo it.this goes together with 1:
onSuccess
does get passed data transformed byselect
at runtime, but not on type level.Beta Was this translation helpful? Give feedback.
All reactions