-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DRAFT] RTKQ Infinite Query integration #4738
base: master
Are you sure you want to change the base?
Conversation
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit dbce780:
|
size-limit report 📦
|
✅ Deploy Preview for redux-starter-kit-docs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
@remus-selea you reported over in #4393 (comment) that:
I just tried reproducing that and I'm not seeing it happen with the current PR. It's entirely possible my test doesn't match what you were doing. Could you give me a repro? |
Knocked out |
Added an example app that ports over the main React Query sandboxes for infinite queries. (Technically "pagination" didn't use an infinite query, and in fact Dominik says you shouldn't use an infinite query for pagination, but I'd already gotten something working so I kept it.) In the process I found out that:
|
As I worked on creating a demo of the bug I discovered more details. The bug seems to only happen for queries that take no arguments. Codesandbox where the issue occurs: https://codesandbox.io/p/sandbox/spring-star-dtc2gy |
hi, I have been using rtk-query, but now I need this function urgently. Is there a timeline for releasing the beta version for infinite query? |
@guaizi149 : no timeline, but the code in this PR works enough that we're asking people to try it out, let us know if they see bugs, and offer feedback on the API design. See the top comment for installation instructions (and be sure to actually look in the CodeSandbox CI job for the actual latest commit installation command). |
Is there any way to access the input parameter provided to the infinite query hook inside of the endpoint definition's Test.tsx
query.ts
|
@agusterodin : you'd have to change the page param type to be |
Oops, my example was screwed up. I meant to have the endpoint generics set up like this:
Are you suggesting to do this? I must misunderstand how infinite queries work because this feels like a workaround.
|
@agusterodin : I think that looks right, yeah. Think of it this way: One other nuance here: There ends up being a separation between the "cache key" value, which is still what you pass to the query hook, and the "initial page param" value. In your case, I think they end up being related - ie, you probably do want to use the filters as the cache key ("all the pages are results for this set of filters"), and then also pass them as the override initial hook value: // The overall cache entry containing _all_ the pages
// will be keyed by serializing `filters`
const { data } = useMyInfiniteQuery(filters, {
infiniteQueryOptions: {
// in order to fetch any given page, we need both `filters` and `offset`
initialPageParam: {filters, offset: 0}
}
}) But yeah, questions like this are also why we're asking people to try this out :) |
Attempting to "port" this Tanstack Query/Virtual infinite scroll example to use RTKQ. Assuming this would be a relatively common use-case. I got stuck at Not sure if it is helpful, but leaving this here to show how i'm attempting to use the infinite query implementation. Will continue testing against future versions and leave feedback! InfiniteQueryTest.tsx
|
@agusterodin and yes, we don't have |
0f1db3d
to
a28de8f
Compare
@agusterodin I just put up a PR implementing all of the infinite query status flags over in #4771 went ahead and merged in the PR - it also fixed a types error after I rebased this branch, and I feel good about the implementation. |
Just tried commit 33e30af. Sorry for delayed response. For some reason the network requests being made to get the "next page" are always the same as the initial request (first page, offset of 0). Note that the code below is based off of the example you provided under the examples directory (not using Tanstack Virtual). InfiniteQueryTest.tsx (barely modified from original example). The only noteworthy difference is that I provide
infiniteQueryApi.ts. Our backend doesn't use cursors, we have an offset (starting index of items to return) and limit (amount of items to return).
Notice from the below screenshot that the Also, another general API question: I have two PS: sorry that all I have is a shitty screenshot and code snippets. I was contemplating setting up a minimal Vite project, but I wouldn't be able to have it send requests to our backend because it is hitting an authenticated endpoint which makes the setup hard to recreate. I would have attached a video screenshot at the bare minimum, but the MacOS video screenshot tool is apparently broken in the current version of MacOS. If what I provided doesn't just contain a trivial error and isn't adequate to help you identify the issue, I will set something better up. |
@agusterodin yeah, if you could give me a project that shows the inconsistent next page behavior, that would definitely help!
As with the other hook options, providing an option in the hook overrides the default value supplied in the endpoint. Think of it as the general equivalent of a default value for a function argument or destructured object field - if you don't pass it in the hook, we have to have something to fall back to. |
Identified issue. If you have the global Uncomment line https://github.com/agusterodin/rtkq-infinite-query-playground |
87270fe
to
79f6ff8
Compare
@agusterodin Thanks for the repro, that made it pretty easy to identify the cause. The internal logic had this sequence: // Start by looking up the existing InfiniteData value from state,
// falling back to an empty value if it doesn't exist yet
const blankData = { pages: [], pageParams: [] }
const cachedData = getState()[reducerPath].queries[arg.queryCacheKey]
?.data as InfiniteData<unknown, unknown> | undefined
const existingData = (
isForcedQuery(arg, getState()) || !cachedData ? blankData : cachedData
) as InfiniteData<unknown, unknown> Problem is that function isForcedQuery(
arg: QueryThunkArg,
state: RootState<any, string, ReducerPath>,
) {
const requestState = state[reducerPath]?.queries?.[arg.queryCacheKey]
const baseFetchOnMountOrArgChange =
state[reducerPath]?.config.refetchOnMountOrArgChange
const fulfilledVal = requestState?.fulfilledTimeStamp
const refetchVal =
arg.forceRefetch ?? (arg.subscribe && baseFetchOnMountOrArgChange)
if (refetchVal) {
// Return if it's true or compare the dates because it must be a number
return (
refetchVal === true ||
(Number(new Date()) - Number(fulfilledVal)) / 1000 >= refetchVal
)
}
return false
} So, it was always opting to use I spent a while staring at it, and eventually changed the line to use a different flag we set internally when you actually call (also I figured out that we have two different |
@remus-selea added several more examples:
|
Pretty significant progress today!
Also did some checks on bundle size. Looks like the new functionality adds 7K min to all RTKQ usages (but only about 1.5K min+gz). The bad news is that's non-negotiable and the increase applies even if you never use an infinite query endpoint, due to the changes currently being deeply embedded inside all of the RTKQ core logic. That said, this PR started with a lot of intentional copy-paste duplication in order to get things working at all. I think I can manage some deduplication and maybe shave off about 2K min of that. FWIW, it looks like React Query's implementation of infinite queries is about 3K min, so this isn't that far off. Theirs is partially shakeable. My general intent at this point is that we'll end up shipping roughly the architectural approach that's in this PR, minus whatever deduplication and byteshaving I can do as cleanup before I decide it's ready. I'm always sensitive about bundle size increases, so I certainly wish I had an easy way to make the bundle size cost opt-in and only if you actually have some infinite query endpoints. At the moment, and with the way this PR is currently architected, I don't have a good idea for how to do that. Longer-term, I've been vaguely tossing around some ideas for seeing if we can pull out some of our functionality based on RTKQ's existing "modules" concept. Currently we just have the "core" and "React" modules. I don't know if it's feasible to make this more shakeable or opt-in, but it's worth investigating. That would happen as part of a future RTK 3.0 effort, no ETA. All that said, given the amount of actual functionality involved here, 5K min seems like a plausibly reasonable cost to pay to add the feature. |
Hi, RTK Query Team! I’m currently implementing infinite scroll in my application using RTK Query, and I’ve encountered a challenge related to editing rows and refetching specific data batches. Here’s the scenario: Infinite Scroll Setup: Data is fetched in batches of 100 rows (e.g., rows 0–99, 100–199, etc.). Users can edit individual rows in the table. I’ve considered using: Tags: Associating tags with specific batches (e.g., batch 200–299). Additional Context: I’m using RTK Query’s useQuery for fetching data and invalidateTags for cache management. |
@Abdullah-Nazardeen pasted your comment over to a new discussion thread in #4801 |
Did a whole of implementation deduplication and byte-shaving. I estimated I could go from +7K min to +5K min. Currently it looks like this PR adds +4.2K min, +1.4K min+gz, so I've knocked off 2.8K min. Given the added functionality, I'm going to say that's pretty good :) |
* Add blank RTK app * Nuke initial examples * Add react-router * Basic routing * Add msw * Configure MSW * Set up MSW * Use RTK CSB CI build * Add baseApi * Add pagination example * Add react-intersection-observer * Add infinite scroll example * Add max-pages example * Drop local example lockfile * Drop back to Vite 5 to fix TS issue * Align Vite versions to fix test TS error
* Extract InfiniteQueryDirection * Export page param functions * Fix useRefs with React 19 * Fix infinite query selector arg type * Implement infinite query status flags * Fix types and flags for infinite query hooks * Add new error messages
…4795) * Consolidate test assertions * Add failing tests for infinite queries vs refetching * Tweak infinite query forced check
* Add bidirectional cursor infinite scroll example * Add example using limit and offset - Add example using page and size - add an intersection observer callback ref hook * Bump infinite query RTK version --------- Co-authored-by: Mark Erikson <[email protected]>
* Fix updateQueryData for infinite queries * Fix upsertQueryData for infinite queries * Fix upsertQueryEntries for infinite queries * Fix types in lifecycle middleware
…le (#4798) * Bump infinite query RTKQ build * Add rn-web and force infinite query example lockfile * Bump MSW worker * Add rn-web types * Expose refetch from infinite query hooks * Add RN FlatList example * Update root lockfile
* Byte-shave infinite query selectors * Export reusable internal selectors * Fix selector skiptoken usage * Use selectors in buildThunks * Byte-shave cache middleware * Byte-shave endpoint assignments * Deduplicate buildThunks * Deduplicate buildInitiate * Tweak TransformCallback usage * Run size checks against the integration branch * Fix infinite query preselector loading state * Clarify endpointName * Deduplicate useQuerySubscription * Move fetch page functions into subscription hook * Deduplicate debug values * Deduplicate useQueryState * Deduplicate hook unsubs and refetches * Add test for initial page param references * Fix cleanup hook issues
91f2cc6
to
2ef6966
Compare
Updated the types to allow providing tags for infinite query endpoints, and added tests to verify that tag invalidation and polling both cause refetches appropriately. |
Pulled latest commit and have been continuing to try to put together a solid implementation of RTK Query + Tanstack Virtual. I used the "infinite scroll" example in the React Virtual documentation as a starting point. Note that my example uses the offset + limit pattern, not the "cursor" pattern. Here is my code so far: https://github.com/agusterodin/rtkq-infinite-query-playground To start, I abstracted the virtualization and query logic into custom hooks so that the component isn't as cluttered and hard-to-follow. I tested and it behaved as expected. Next, to make the example more realistic and comprehensive, I added some filters (a debounced searchbox and standard checkbox). Here are a few issues i'm facing: Suggested way to resetting virtualizer scroll position when data comes back from a different combination of filters? This is a solution that kinda works, but feels insanely hacky. I put detailed comments inside the code of what I have tried and potential alternatives. These notes are towards the bottom of app/components/useInfiniteVirtualizedNotesList.ts.
How to show loading spinner when loading new combination of filters? If the first request for the current filters aren't loading, don't show loading spinner, regardless of any fetching that may be happening for old filters (eg: slow request). Is using Changing filters a few times causes more pages to constantly keep loading This very well could be my fault. I may have made some sort of mistake when porting the example from Tanstack Query to RTK Query. As far as I could tell, everything was working before I added the additional filters (searchbox and checkbox). It also could be the way I added the filters, but I don't see anything that jumps out as immediately obvious. I would be glad to continue keeping you updated as I add to my example. A working example of virtualized infinite scroll with filters and textbox debounce may be really nice to include alongside the release of this feature :) |
@agusterodin as a reminder, I have no actual experience using infinite queries at all :) which is a major difficulty point trying to implement them. My main question here would be: how would you implement any of that using React Query instead of RTK Query? And can you point to any apparent differences in behavior between the two? My overall goal is not "guarantee we handle every single hypothetical use case", but rather "match React Query's behavior, API, and known use cases", under the assumption that API design and behavior is good enough for 90%+ of infinite query use cases out there. Off the top of my head, I don't think we actually expose any field that directly shows "you just changed from arg A to arg B". We do have
Not sure I follow that statement. The flags should be getting derived from the "current" cache entry, which (for a brand new cache entry) will be
Yeah, I've got that infinite query example app in the PR under |
@markerikson Roger. I'm in a similar boat. We currently only have one virtualized infinite scroll list in our application, but plan on using them more in the future as our databases get saturated. The one virtualized infinite scrollable list we have is powered by Redux Saga (only usage of saga across all of our codebases now) and is buggy af. For 1st issue (reset scroll on filter change): wasn't sure if there is something RTK exposes that would be a better source to know the filter combination of the data that is currently being displayed by the hook. Using For 3rd issue (unnecessary requests being sent): I don't currently use React Query on any project and have no familiarity, but will attempt to port my example back to React Query and see if it exhibits the same behavior when filters are incorporated. I'll take note of differences in behavior (if any). I don't want to clutter your PR comments anymore, so unless I find something noteworthy, I will open a GitHub discussion and post there instead. I really appreciate your hard work on this feature!! |
Thanks, and I seriously appreciate having you actually putting a ton of work into trying this out in real-world situations! Even if I don't have actual answers for your questions, it's incredibly helpful to know that it's actually being tried out by someone else, and the implementation works enough that we're down to these kinds of complicated-but-presumably-rare edge cases. |
@markerikson I wanted to let you know that we tried using this new infinite query for an "Instagram-style" infinite feed in our app and, so far, it works way better than the manual merging workaround we were previously adopting :) |
@markerikson I've started using tag invalidation to automatically trigger refetches for the infinite query data, and it's been working smoothly. I'm really pleased to have this feature as it allowed me to get rid of a workaround I had in place. I haven’t gotten around to trying out I’m grateful for all the time and effort that’s been put into this! |
@markerikson I've also replaced old workaround with infiniteQuery and it works great. In my workaround I did not used refetch by tag invalidation in my infinity list. With infiniteQuery it works great. I was really waiting for this. Big thanks to you. Great work. |
Based on the comments here, it sounds like the actual implementation is probably about done code-wise! I've started work on adding docs for the infinite query feature. WIP draft is here: |
Just small issue. It seems like there is missing typing for providesTags option for infiniteQuery. It seems that it is typed as |
@DominikBrandys good catch :) I merged #4812 last week that fixed those invalid |
Have been working on adding filters (a debounced searchbox and simple checkbox) to the Tanstack Query + React Virtual example (from their docs) so I can compare to my test of RTKQ + React Virtual that has filtering functionality. I think i’m noticing some undesirable differences in behavior. Still trying to narrow down what’s going on before speaking too soon, but will double check my code to make sure no glaring issues on my end, push my changes to reproduction branch, and write something up as soon as possible (Wednesday at latest) |
Just merged in the docs PR. Added API ref coverage for infinite queries in I'd appreciate any feedback on what else should be added to the docs, if anything! |
@markerikson Maybe it's time to update the comparison table "Infinite scrolling" feature! :) |
I'm just trying this out in my app - I'll be very pleased to be able to remove our custom solution for this, so thank you! Just to note that I also came across the issue addressed in this comment. i.e. I have some parameters that I need to use as part of the cache key, and which need to be passed into the request body. These are options for filtering and sorting etc which I wouldn't think of as being part of the pagination params. The solution given works for me but it doesn't feel very natural. In the example above, it feels a bit awkward to have to pass My other question is about flattening the results. The example in the docs just uses |
Would be great if // api.ts
getChatMessages: build.infiniteQuery<PageData, string, PageParam>({
infiniteQueryOptions: {
initialPageParam: { chatId: "", cursor: "", direction: "none" },
getNextPageParam: ({ endCursor }, _, { chatId }) => {
return { chatId, cursor: endCursor, direction: "after" };
},
getPreviousPageParam: ({ startCursor }, _, { chatId }) => {
return { chatId, cursor: startCursor, direction: "before" };
},
},
async queryFn(pageParam) {
const result = await fetchMessages(pageParam.chatId, pageParam);
return { data: result };
},
});
// usage.tsx
const chatId = window.location.pathname.slice(1);
const { data } = useGetChatMessagesInfiniteQuery(chatId, {
initialPageParam: { chatId, cursor: "", direction: "none" },
}); |
@ensconced , @nowaylifer : I'll try to put a bit of thought into it, but off the top of my head I'm not sure how we'd be able to change the API design or implementation around passing through the page params. I'll double-check vs how React Query works, but I think you'd have the same issue there.
Per the initial thread comment, we did discuss adding some kind of a selector definition into the infinite query endpoints, but I decided it wasn't critical enough to hold up the initial release. For now, the simplest approach is to do the restructuring of the data in the component - either directly in rendering, in a |
Overview
This is a running integration PR for the upcoming RTK Query support for "infinite / paginated queries".
The overall plan is to reimplement React Query's public API for infinite queries on top of RTK Query's internals.
This work was started by @riqts in [API Concept] - Infinite Query API. I picked up that work, rebased it, and have fleshed out the TS types, tests, and implementation. See that PR for the original discussion and progress. Also, thanks to @TkDodo for the advice on how they designed the infinite query API for React Query!
My current somewhat ambitious goal is to ship a final version of infinite query support for RTKQ by
the end of this year"early" 2025. I am absolutely not going to guarantee that :) It's entirely dependent on how much free time I have to dedicate to this effort, how complicated this turns out to be, and how much polish is needed. But in terms of maintenance effort, shipping this is now my main priority!Status
It currently works sufficiently that it's ready to be tried out in example apps so that we can confirm it works across a variety of use cases, and find bugs or areas that need work. It's not ready for an official alpha yet.
You can try installing the PR preview build using the installation instructions from the "CodeSandbox CI" job listed at the bottom of the PR. Please leave comments and feedback here!
I've got a laundry list of examples and tests that ought to be added - see the "Todos" section below. Contributions are appreciated!
Preview Builds
Open the "CodeSandbox CI" details check:
Click the most recent commit on the left, then copy-paste the install instructions for your package manager from the "Local Install Instructions" section on the right, such as:
Todos
Jotting down some todos as I think of them:
Functionality
gN/PPP
whenmaxPages
> 0isFetchingNext/PreviousPage
flagshasNext/PreviousPage
flagsInvestigate movingThis probably still makes sense to keep with the data in case of users upsertingpageParams
into some new metadata field in the cache entry, so that it's not directly exposed to the user indata
(per discussion with Dominik)Possibly some kind oflet's not worry about this for the initial releasecombinePages
option, so that you don't have to doselectFromResult: ({data}) => data.pages.flat()
(and memoize it) for every endpointtransformResponse
interactionsdoesn't make sense conceptually for infinite query endpointsmerge
interactionsinfiniteQuerySlice
Movemaybe doable, but complicatedfetchNext/PreviousPage
into thunk resultsTests / Example Use Cases
React Query examples
ref:
Other
upsertQueryData
/upsertQueryEntries
?