-
Notifications
You must be signed in to change notification settings - Fork 181
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
pageInfo.hasPreviousPage is always false if you are paging forwards #58
Comments
I know this seems counter-intuitive to you as it did to me as well, but that is actually how it is supposed to work...
https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo.Fields Sorry I couldn't use markdown in the post I emailed. |
Thanks for the response, Matt. So its the actual Relay spec that has a bug. :( In any case, it makes no sense, and ruins bi-directional paging. Since the maintainers of this repo are the same guys maintaining the spec, I'm gonna leave this open, as it really needs to be addressed. |
Yeah, this is good feedback. We went with this approach because it's easy to envision a backend system (and we had some at Facebook) where it was be prohibitively expensive to compute Your feedback is spot on, though; this definitely messes with bidirectional pagination. One way forward would be to modify the Relay specification and allow, though not require, Thoughts? |
Thanks for the reply, Dan. Loving the Relay. tl;dr: connectionFromArray should assume that its got an entire dataset, so it can be honest about hasNextPage/hasPreviousPage. connectionFromArraySlice should assume that its got only part of a larger data set, so it cant be honest about hasPrevious page when its going forward (or vice-versa). At the end of the day, to make good on the Relay goal of making data fetching/caching as easy and opaque as possible (which is an awesome goal), paging just "needs to work". And that means supporting the commonly understood ideas of what paging in apps looks like, which is pretty much either infinite scroll (forward-only) or next/prev (bi-directional) with/without page numbers. We can check off forward-only because that works great! ;) I was messing with arrayconnection.js and it was pretty simple to stop taking last/first into account when calculating and comparing the array bounds and the current cursor location. Relay on the client thwarted me, however, because it still overrides the values for hasNextPage/hasPreviousPage on the client based on whether first/last is being used. I guess I don't understand why connectionFromArray can't give back honest values from hasNextPage/hasPreviousPage. Its assumed that we are passing in the whole list of data as the array. Why not be honest with the hasNextPage/hasPreviousPage? connectionFromArraySlice can and should be used when we are using forward-only paging on large data-sets. The comments in the code come out and say as much. It kinda looks like we just found an easy way to have connectionFromArray delegate its work to connectionFromArraySlice. And connectionFromArraySlice correctly assumes it cant honestly answer if there is a previous page when going forward on what it has been told is just a slice of a larger array. Maybe some renaming would make things clearer. connectionFromArray becomes bidirectionalPagingConnection (or entireDataSetConnection or something) and connectionFromArraySlice becomes forwardOnlyPagingConnection (or partialDataSetConnection or something). And then change connectionArgs validation. In fact, Im not convinced we need both 'first' and 'last' if we can just make some assumptions. Instead of first/last we just have 'count', because thats what both those are: just a count of records to return. Then if we have count without before or after, we assume its going forward from the start (which I think is the 99%+ use case). If we want to start out going backward from the end, you pass a 'before: end' (or before: '' or something). Other than that, before and after cursors work the same, and args validation just changes to not allow both before and after together. And then change the Relay spec to reflect these changes. ;) (And then figure out page numbers, which once we decide that connectionFromArray is meant for entire datasets, is an easy prop to add to pageInfo and connectionArgs). To maybe help out anyone currently wrangling with this same issue, here is a link to a gist that shows how I am hacking together next/prev paging in Relay. It was heavily informed by this fine gentleman's efforts with the same problem. Anyway, thanks for the listen. I'm having so much fun getting into Relay and I love what it does and what its going to do. Rock on, rockstars! |
I am trying to get |
Ugh, this issue is so deeply annoying when having a hard requirement to do windowed pagination due to limited space. Even with a server returning |
The pageType and hasNextPage/hasPrevPage is declared in connection.js, but the logic appears to be in arrayconnection.js graphql-relay-js/src/connection/arrayconnection.js Lines 66 to 126 in 997e069
This code here looks suspect( return {
edges,
pageInfo: {
startCursor: firstEdge ? firstEdge.cursor : null,
endCursor: lastEdge ? lastEdge.cursor : null,
hasPreviousPage:
typeof last === 'number' ? startOffset > lowerBound : false,
hasNextPage:
typeof first === 'number' ? endOffset < upperBound : false,
},
}; Specifically , the if (typeof first === 'number') {
endOffset = Math.min(
endOffset,
startOffset + first
);
}
if (typeof last === 'number') {
startOffset = Math.max(
startOffset,
endOffset - last
);
} The var lowerBound = after ? (afterOffset + 1) : 0;
var upperBound = before ? beforeOffset : arrayLength; Looks like you need to set |
This is really annoying - I had to implement my own methods, even though I can generate these values properly in the GraphQL response:
I have a feeling that this will be a continued problem, or people will choose to not adopt Connections / Edges, and instead just paginate via hints from parameters in a Route. |
The other workaround is to put state of graphql query in the url
Going forward works well as it is, to go backward simply go back in history, i.e. If ?after= is not present, Previous button can be hidden |
Any status on resolving this? |
@ivosabev: I'm not aware of anybody actively working on this, but given Dan's comment (quoting below) I think it would be reasonable to accept a PR that modified the spec:
|
Any update or work around on this issue? @gallagher-stem comment seems like a good solution, |
I have PRed a change to the spec to permit setting these values properly so that at least other implementations can do so without breaking technical compliance: facebook/relay#2079 |
Summary: The existing specification for PageInfo requires that `hasPreviousPage` be false when paginating forward, which is quite limiting. Adjust that part of the specification to permit setting `hasPreviousPage` when paginating forward, and symmetrically permit setting `hasNextPage` when paginating backward. Implementations may still hard-code false if calculating the correct value would be inefficient. This was suggested by dschafer in graphql/graphql-relay-js#58 (comment). Making this change exposed an inconsistency in handling unusual cases such as when a client provides only `last` and `after` arguments. I adjusted a few other parts of the algorithm to clarify how best to handle these cases. cc swalkinshaw Closes #2079 Differential Revision: D5811407 Pulled By: leebyron fbshipit-source-id: 431335dd07cb5c2536d6bfdb8fc1b4a7e4900828
Are PRs for this still welcome or should this issue be closed as won't fix? |
Any plans to change this still? |
According to spec:
That should mean hasPreviousPage can be true, when you know there are edges before the current dataset. As of right now, I'm using something similar to @staylor |
@staylor I know this was a while back now but your const idxPrefix = 'idx---';
const fromBase64 = (encoded) =>
atob(encoded);
const indexFromCursor = (cursor) =>
parseInt(fromBase64(cursor).replace(idxPrefix, ''), 10);
const hasPreviousPage = (startCursor) =>
indexFromCursor(startCursor) > 0;
const hasNextPage = (endCursor, arrayLength) =>
(indexFromCursor(endCursor) + 1) % limit === 0;
// Assuming 12 items in the array and pages (limit) of 3
console.log(hasNextPage(btoa('idx---2'))); // true
console.log(hasNextPage(btoa('idx---5'))); // true
console.log(hasNextPage(btoa('idx---8'))); // true
console.log(hasNextPage(btoa('idx---11'))); // true, should be false Aside, to be fair I don't expect you thought anybody would copy paste it like I did 😆 I'm instead computing it based on the array length: const idxPrefix = 'idx---';
const fromBase64 = (encoded) =>
atob(encoded);
const indexFromCursor = (cursor) =>
parseInt(fromBase64(cursor).replace(idxPrefix, ''), 10);
const hasPreviousPage = (startCursor) =>
indexFromCursor(startCursor) > 0;
const hasNextPage = (endCursor, arrayLength) =>
(indexFromCursor(endCursor) + 1) < arrayLength;
// Assuming 12 items in the array and pages (limit) of 3
console.log(hasNextPage(btoa('idx---2'), 12)); // true
console.log(hasNextPage(btoa('idx---5'), 12)); // true
console.log(hasNextPage(btoa('idx---8'), 12)); // true
console.log(hasNextPage(btoa('idx---11', 12))); // false |
Any updates? Really hope we can fix this... |
That's not true. It always tells you if it has previous page, but not when using connectionFromArraySlice |
I solved it with this (I think)
|
graphql-relay-js/src/connection/arrayconnection.js
Line 120 in 7422cfe
If my connection args don't have the 'last' parameter, then hasPreviousPage will always be false. Same problem if you are paging backwards and don't have the 'first' parameter in your connection args: hasNextPage will always be false.
But I can't have a 'last' parameter if I am paging forward using 'first' and 'after'. And I can't have a 'first' parameter if I am paging backwards using 'last' and 'before'. Babel-relay-plugin will throw an error on transpile.
So if I am paging forwards, I will always be told I have no previous pages, even when I do. And if I am paging backwards I will always be told I have no next pages, even when I do.
This has gotta be a bug. It kinda ruins bi-directional paging.
Can't we just make paging easier and let us pass first and last and before and after all as connection args (some of them as null depending on which way you are paging) without babel-relay-plugin blowing up?
The text was updated successfully, but these errors were encountered: