-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
[meta] Support windowed pagination #540
Comments
Thank you for your summary @josephsavona. Having read #466, the connection specs and docs, what I wonder most is how it might be possible to reconcile these two paging methods while keeping cursors opaque. If cursors are opaque, jumping to arbitrary pages seems impossible. The thing about the opaque cursor-based approach as I understand it is not just that it reflects the infinite scrolling use-case. Given that Relay came from Facebook—a massive distributed system—I assumed its cursor-based paging is more significantly related to the peculiarities of paging in distributed systems: specifically that skip/limit paging is non-performant in distributed applications. The issues are described in this blog post about MongoDB paging, but basically any distributed DB that I've played with has warned about skip/limit paging for this reason[1]. It might make sense to think about this issue from this angle. [1] c.f. Elasticsearch paging |
@dminkovsky Yup, we use cursor-based pagination precisely because skip/limit isn't performant in large data sets. Also, skip/limit can return overlapping results if items are added between fetching pages. One option might be to make connection handling injectable. Something like @yuzhi - thoughts? |
I'm much less cool than @yuzhi, but I've been prodding at this a bit and have some thoughts. I think there's really 3 kinds of common pagination patterns: page number pagination, limit/offset pagination, and cursor pagination. As a reference point, DRF is fairly comprehensive and implements all three (though its cursor-based pagination approach is not directly compatible with Relay's assumptions because it only provides start and end cursors). Relay already handles cursor pagination just fine, so we don't need to talk too much about it, except mention that most cursor paginated REST APIs actually only provide start and end cursors rather than per-element cursors. Page number based pagination seems like it'd be really "easy" in some sense to handle in Relay - your queries would take the form of Limit/offset pagination in this context actually seems very similar to cursor pagination; it seems like essentially the same as cursor pagination, except that (1) the cursors are non-opaque to the client, and (2) the cursors can change underneath the client as records are inserted and removed. One complexity in both cases is how to handle new elements getting inserted into the collections, but frankly neither method of pagination really deals well with dynamic lists anyway. Partially, #466 I think just speaks to the difficulties of trying to do window-based pagination when using cursors. I think that complexity is more at the application layer conceptually though; imagine the following:
I think there are meaningful practical difficulties with windowing on cursor-based APIs, which make it a bad enough fit that it might be better to not try to shoehorn it in. |
To add: IMO one of the complexity points in Relay with e.g. automatically discovering which new nodes to fetch when using cursor-based pagination for infinite scrolling is just largely not relevant when using windowed pagination (via either page numbers or even limit/offset to an extent). That level of rich support just isn't as relevant in the windowed case. |
@taion Agreed, these are distinct use cases and ultimately Relay should support all of them. Allowing connection handling to be injectable would make it easier for products to choose between approaches, without having to build both models (page number & limit/offset are isomorphic) into the core and test them separately. Note that connections account for much of the complexity in Relay internals, so testing against one well-defined injection API is preferable to |
That's not exactly what I'm saying - I feel like the current pagination API offers enough to (with at most minor tweaks) satisfactorily implement page-based pagination and limit/offset-based pagination in user space. Limit/offset might have slightly different semantics, but it seems perfectly suitable to model e.g. page-based pagination as just another argument to the current connection style. |
I also would like to be able to jump to a specific page. @taion - you write above "I feel like the current pagination API offers enough to (with at most minor tweaks) satisfactorily implement page-based pagination". Can you please explain how to achieve this? Many thanks... |
You just add a |
to a GraphQLList-type field, right? And then just do whatever, right? On Wed, Dec 16, 2015 at 10:56 AM, Jimmy Jia notifications@github.com
|
Pretty much. You can make it a connection if you want connection-style behavior on mutations... depends what you want, really. But windowed pagination is in some sense easy - it's just a custom filter arg. |
@taion - that's indeed easy - my concern was that using some arbitrary page parameter rather than the cursor I would be losing the benefits of the connection type. If there are no such benefits, then not even sure why bother with connection in the first place rather than just some standard field...? |
Which specific benefits were you thinking about that would be relevant when doing windowed pagination? |
Not sure. I'm really new to Relay and might not have enough understanding of all the concepts, but I read somewhere that Connection was created to work well with large datasets. But if not using the Connection mechanisms and instead just using some page number parameter, is there any reason to stick with a Connection rather than a standard field? |
You get nice stuff like just inserting new edges after mutations. Otherwise there are great conveniences for infinite scroll type views. But if you're just doing windowed pagination, I don't think it matters much. |
@taion Thanks for your help. I'll check if I can relax my requirements and maybe just use what Connection provides. Maybe indeed in large datasets it doesn't make much sense to allow the viewer to "jump" to a particular page anyway (especially if the dataset is not fixed, in which case next time you will get different results anyway)... |
@taion Can you please clarify what the nice stuff is exactly? |
That doesn't really make sense in the context of windowed pagination. Suppose you're on page n. If an insert happens, where the new node is not inserted on this page, what should you do? That's why I say it's not particularly well-defined. |
@taion Thats true, even though in some of my previous apps the visible window would be updated to reflect inserts and updated correctly. But that might be actually a bit out of scope here since it usually also requires a "real-time" connection or notifications from the back-end. So what you are saying is: forget about connections and implement a simple windowed pagination as a simple query for a list - because connections do not provide any benefit in this case? |
I think if you're doing inserts or deletes, using a connection will still be more like what you want – it's just that there will be additional edge cases to think about in the context of insertions and deletions. You're probably going to just end up re-fetching that entire page on insertions or deletions, which is probably what you want anyway. |
Thanks @taion. There is only one last thing I am concerned about - memory. What if a user pages through huge amounts of rows - maybe even in different connections - will the store get bigger and bigger - or is there some kind of garbage collection in the Relay Store as well? |
You get the same thing no matter what pagination scheme you use. |
@taion I thought that Relay Connections might be able to remove unused edges from the Store. But maybe memory concerns is a totally different discussion. |
I'm going to close due to inactivity. However, in the new core we've developed a more generalized abstraction of pagination/connections that should allow developing windowed pagination in user-space. We'll document and revisit once the new core is available (#1369). Thanks all - we really appreciate your being vocal about this use-case. |
same issue. I need to jump to a specific page when using relay-style cursor-based pagination. For example: first page(first: 10) => page 10(first: 10, after: ??), How can I calculate the cursor? I have a pagination UI component like this: So, How can I achieve this? Thanks. |
What you want is something called Based on the After that, to jump into a specific page, you probably gonna need a |
@jgcmarins Thanks. Should this Relay Cursor Connections Specification need to be updated? Or, there will be a new version Specification? I didn't find |
Specifications are right. |
In case anyone finds this useful, wrote a blogpost on using Relay/GraphQL for windowed pagination cc @mrdulin https://artsy.github.io/blog/2020/01/21/graphql-relay-windowed-pagination/ |
Hi, I try to implement a pagination table with usePagination and if everything works perfectly for next and previous page, I can't find the good way for "go to page" or "last page". I have checked all the project, documentation, links... and nothing help me for now. Hooks simplify relay but it stays infortunately hard with uncomplete up to date documentation. I know it's time but if we want membership... Anyway, thank you for all this amazing project. |
I resolved this getting the total count of items with that I get the number of pages, and the page you click * number of items per page, and calls the query with variable first = page number * number of items per page and pass also last with the number of items per page. Example 100 items 20 per page 5 pages click page 3 it will 3 * 20 = 60 this value it will be first and last 20 you get the items from 40 to 60. |
Relay's pagination model is optimized for infinite scrolling, in which a view requests an increasing larger number of items. A common requirement is windowed pagination in which the UI shows pages of e.g. 10 items, with support for jumping to the first/previous/next/last page (or to an arbitrary page number in between).
This is currently difficult to implement in Relay (see #466 for a writeup by @faassen).
Challenges include:
first/after
andlast/before
arguments in the same field so long as values are only provided for one of these pairs. This is currently prevented inbabel-relay-plugin
; the check should be moved to e.g.GraphQLRange
.after
orbefore
argument value when jumping to an arbitrary page (more generally, how to do offset based pagination over a cursor-based schema).hasNextPage
andhasPreviousPage
provide meaningful values - the connection spec currently states that the value ofhasNextPage
andhasPreviousPage
must be returned asfalse
unless the user is paginating in the correct direction, even though there may be previous or next edges.The text was updated successfully, but these errors were encountered: