RTKQ "infinite query" potential use cases and concerns #3174
Replies: 11 comments 14 replies
-
I use RTKQ with React Native's
From my understanding, RTKQ could accommodate the RN Presently I use approach 3 and accept the constraints. |
Beta Was this translation helpful? Give feedback.
-
The problems I'm with infinite scrolling "adjacent" behaviors is they don't really mix well right now with the declarative syntax of RTK query hooks, which is its strength. I haven't completely touched every part of our codebase, but essentially we have one scrolling list of search results represented by AG Grids, and we also have a scrolling list of widgets that are laid out in a grid, so there are several per row. The search results are my architecture, and the widgets are legacy and I haven't figured out a time-efficient way to migrate them to rtk query. They are actually infinite scrolling, but the search results I decided in version 1 to just load the whole list and we'll worry about infinite scrolling when we get performance problems. However, I can already see some of the issues. I'll first talk about how the search results are currently implemented. I went with the approach from Redux essentials, where I already have a list of data sources to search, and I create a scrolling list that maps those data sources to components that get a data source id and a search term. They then individually run their searches and present the results. And theoretically this could work with infinite scrolling, because they could be virtualized and the query would run only when the list item is drawn by the container. But I think we need a bit more fine-grained control over subscriptions for this to truly work, like separating out requesting the query rerun on prop change or on mount. I also found anything that required working with these queries from a higher level (like aggregation, showing a spinner if any query is running, or selecting all search results) to be extremely painful. It would be good to have some kind of simplified syntax for selecting or adding additional reducers for all states that are for current subscriptions to a given endpoint. Even better would be something that would allow a match on a partial argument (such as the search term and get all of the calls to that endpoint with the search term, regardless of datasource). In a side note, the ergonomics of trying to have state for each item of a scrolling list that also has an rtk query on it is awkward. I wind up having a bunch of entity adapters, and then pulling, for example, the selected items in the grid that represents that item, from the state created by that adapter by id. While I have no issue with this, my team has a hard time understanding why we have two different ways to access the state that don't touch and how to use those together. And the business is moving me out to a new product and I will only be able to clarify so much. It would be good if there were some way to either attach more state to a query somehow or get enough information about a query that you could look at it/use it from within a slice. Back to infinite scrolling--when I tried to retrofit our legacy code, I ran into the issue that our old code wanted to run a fetch, attach it to the bottom, run a fetch, attach it to the bottom, and so on. Which is fine, but when you're using rtk query, you really need to have a representation of each query you want results for in the component. So the issue was really "how do I represent some unknown number of queries and then write a selector that returns the results of all of them in the right order," along with all the subscription issues that come along with that. This kind of comes back to the idea that we need to be able to treat a series of related calls as a collection, but in this case we also need to be able to make the calls as a collection as well, rather than just needing to be able to select them together. So, idk, maybe you could have myItems.map(item => makeQueryHere)...? Or something...? And then for infinite scrolling to work, you would just add more items from your master list to the list you're making queries about in response to whatever events on the UI that are triggering your scroll. And if you don't have a master list, you still would have some sort of initial call that tells you how many records to get that you could use to generate items (or if your system is to keep hitting until nothing comes back, you just add a new item with new offsets until that point). This would be fairly idiomatic and should feel familiar to people who work with React all the time. |
Beta Was this translation helpful? Give feedback.
-
Hello! Good initiative! However, after reading the long discussion in #1163, I think now, that it would be better to let source-code talk instead of developers! A minimal example would be pretty useful, I'd use React Native Web to build something for mobile and web. In addition, a channel on youtube with ordered videos about introducing RTKQ from docs, explain diffs with equivalent tools (React Query, Apollo etc..), then a hands-on projects with different behaviors 🔥 . Personally, I've experienced using RTKQ in a mobile app using React Native FlatList to make list that start with limited number of elements (e.g: 5), and below there is a fixed button "Load More", each time I press on that button, I get more items till I reach list length. I think about sharing such app, starting with straighforward approaches to complex ones, and it might be interesting to improve it later. I hope this discussion wont be a copy like #1163 |
Beta Was this translation helpful? Give feedback.
-
For those unfamiliar, let me quickly describe the design choices and tradeoffs in we treat an infinite query as one query that's chunked into multiple requestsThis is important, because it means at the end of the day, you only have one cache entry. That entry is chunked into multiple pages, and we offer the option to This doesn't suffer from not-being-subscribed, but it has the trade-off that you only have one cache entry, which means that if you want to refetch, all pages are being refetched, one after the other. This is usually fine, but people have been requesting to "only" refetch the first page. We have added such a capability to some functions, but will remove it in v5 because it might mean duplicated or missing entries in your lists. It's a dangerous footgun. so our cache structure is usually:
where you provide the way how to get to the next pagewe don't care about paged, cursor or offset based pagination because react-query doesn't do the data fetching. Maybe this is different for rtk-query. What you have to provide is a function that computes the next
given that each fetch returns
pretty much the same as the offset based but without the limit
new in v5: maxPagesbecause keeping lots of pages in the cache might make rendering slow, and might also trigger lots of refetches (see first point), we've decided to add a With this, you can keep the memory footprint low and reduce amount of necessary refetches (because only each stored page will be refetched). It's kind of the replacement to "I don't want to refetch all 100 pages in the cache on window focus", because it will at least keep your data consistent, and you can decide how many pages to keep. Advantages of this approach is that you have one cache entry (no problem with needing to subscribe to multiple queries or loosing subscriptions), so it renders like "any normal query", it just so happens that your data is chunked into an array with multiple entries. Disadvantage is that there is only one cache entry 😅 so with refetches, it's always all-or-nothing. If you have 5 pages and you refetch them, if the last one errors, your whole query will be in I haven't heard any complaints about that so far though, because this is mostly related to refetches, and while that is going on, the user will always see stale data, so they won't notice. Even if something errors, it's a "background" error, so you can still display the old data. If you start out empty, the first page is fetched, and it is rendered. Then, if you invoke Okay finally, here's the link to our official codesandbox example that shows bi-directional load more where |
Beta Was this translation helpful? Give feedback.
-
In our apps we often use a "skip" param and a "top" param, that we send to the backend with odata queries.
For now this is possible to implement with rtkq using the newly added serializeQueryArgs, merge, and forceRefetch options, but the API is a bit unclear to me and I'm not sure I'm doing it correctly... The React Query approach sounds nice though :) |
Beta Was this translation helpful? Give feedback.
-
Interesting article about using React-Query pagination with an API that only returns "new" items based on an https://mmiller42.medium.com/polling-for-new-content-using-react-query-useinfinitequery-18395bb98894 |
Beta Was this translation helpful? Give feedback.
-
Based on the comments on this SO question, it seems like invalidateTags doesn’t play nice when using |
Beta Was this translation helpful? Give feedback.
-
A different scenario: I'm building a Mastodon client and Mastodons REST API does not have the concept of
For example, requesting the first 20 items of a public timeline is a The solution offered in the mentioned SO question works well, the only part missing being the filtering of duplicates inside the At work, our teams use jsData, an old(ish) library that does basically the same as RTKQ is aming for. In jsData we can declare which property should act as a primary key( EDIT: I just found out about |
Beta Was this translation helpful? Give feedback.
-
My pain point is infinite scroll with filtering(some filtering, sorting, searchQuery params). If one of those changes I want to start from the begging. I have infinite scroll and use merge cache data for this purposes. But I also have filter and sorting and if one of them is changed I want to clear cache and start fetching from the begging new data according to the new params. I'm expecting that there is an easy solution out of the box, but I haven't found it yet.
so when someFilterIds or sortBy changes I need to clear cache. Now it's simply add more data to the cache when these params are changed. Is there a way for a moment to archive this? |
Beta Was this translation helpful? Give feedback.
-
Another idea to add to the list of ideas: handle infinite queries as a new type of query endpoint, split up into three different responsibilities:
Important here: pages are stored independently and are only stitched when the selector is called, we do not merge state. An api for that could look like this: createApi({
endpoints: (builder) => ({
paginated: builder.inifiniteQuery({
selection({ from, to }, { read }) {
let pages = [];
while (from < to) {
pages.push(read({ offset: from, count: Math.min(to-from, 20) })
from += 20;
}
// returning an array of "read" elements will call `query` for all of them that are not cached yet and passes them to `merge` then
return pages;
},
query(pageDetails) {
// just a normal query function
},
merge(fetchedPages) {
// merge them here for use as a selector result
}
}),
cursorBased: builder.inifiniteQuery({
async selection({ startCursor, num }, { read, updateProgress }) {
let pages = [];
let totalCount = 0
while ((totalCount = arrayLenSum(pages)) < num) {
// could also `await read` to get access to the response and extract infos required to get the next page
pages.push(await read({
cursor: pages.at(-1).nextCursor,
count: Math.min(totalCount-num, 20)
})
// could use something like `updateProgress` to already make an intermediate state available while loading
updateProgress(pages)
}
// passes it on to `merge`
return pages;
},
query(pageDetails) {
// just a normal query function
},
merge(fetchedPages) {
// merge them ehre
}
}),
})
}) Open questions: how to iterately "fetch forward" and "fetch backwards" from a given result - do we add more option-functions in here, and expose those on the return value of |
Beta Was this translation helpful? Give feedback.
-
Here is my use case. I am implementing Datagrids using Material UI's DatagridPro component. I use a callback when the datagrid has reached the end of the page. It has the same problem as @adamhari pointed out here:
As for my api, we use the JSONApi spec, so we use pageLimit to fetch a certain number of items and we increment/decrement pageOffset to get the next/previous page. Hope this helps |
Beta Was this translation helpful? Give feedback.
-
We've got a ton of discussion over in #1163 around all the different ways people want to do infinite queries, and the workarounds they've come up with to try to implement them using the current capabilities in RTKQ.
In that thread, Lenz said:
I think it might be helpful to start a new discussion just for listing specific use cases and variations in infinite query scenarios, then use that to brainstorm potential API designs.
Related to that, Dominik from React-Query is looking at redesigning their infinite query support for React-Query v5, so this might be a useful resource on both sides.
So. What are all the different ways people want to try to do and use infinite queries today?
It would also be very helpful if someone could do a roundup of how the other data fetching libraries currently support infinite queries. What do their APIs look like? What are the limitations?
Beta Was this translation helpful? Give feedback.
All reactions