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
SSRandpersistenceare affected. See this old issue from 2020:The root of this problem stems from the fact that we leverage
undefinedas the default pageParam. When you fetch the first page, you getundefinedpassed into thequeryFnaspageParam. This is convenient because you can now assign a default value for the first page, as shown in the docs:pageParam = 0works because we give youundefinedfor 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:undefinedis problematic when serializing as json, which dehydration does.For SSR, nextJs cannot dehydrate
undefinedand 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 getundefinedpassed into thequeryFn- you'll getnullinstead. 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
getNextPageParamorgetPreviousPageParam. 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
undefinedand instead requiring a new optiondefaultPageParampassed touseInfiniteQuery. The above example would become:Not only will this side-step the issue (because we don't store
undefinedat all), It might also close a loophole in the typings, aspageParamis currently defined asany. We could add another genericTPageParam, which should be inferred from thedefaultPageParam- and it should also be whatgetNextPageParamreturns (TPageParam | undefined).To fix issue two, the proposal would be to remove the possibility to pass manual
pageParamtofetchNextPage. 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
getNextPageParamfunction 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
getNextPageParamorgetPreviousPageParamrequired on type level.This removal brought up the question of why we even need to store
pageParamsat 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 thedefaultPageParamof0.Alternatives
No alternatives considered for issue number 1
For issue number 2, we could decide to not store
pageParamsat all, but instead only store thepageParamof 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 allowuseInfiniteQueryto 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:
selectneeds 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:
onSuccessdoes get passed data transformed byselectat runtime, but not on type level.Beta Was this translation helpful? Give feedback.
All reactions