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
excerpt: 'How we changed our pagination from infinite scroll to windowed pagination using relay.'
date: '2021-12-11T12:15:00-0800Z'
Windowed Pagination with Relay
Relay-style pagination is a popular option for paginating graphql queries. It differs from offset-style pagination in the sense that it uses cursors to keep track of the start and end of pages as opposed to offsets. This allows you to begin pagination at a particular node as opposed to a particular offset. There are a number of tradeoffs between the two styles of pagination. More details can be found here.
As mentioned in the article above, relay style pagination is uni-directional by design. This means that it's especially apt to infinite-scroll UI, but not so much for classical windowed-style pagination. That's not to say that that's not possible.
Problem
Our systems were initially built with infinite-scroll, but over time we realized that infinite scroll did not make sense for our systems. Infinite scroll UI is more suitable for data that's changing quickly, and less suitable for tools where you may want to pick-up where you left off, and have the page be bookmarkable. Additionally, we are not a social media app, and we are not optimizing our data for discoverability, but rather for organization (sorting, filtering, etc). For this, we wanted to move toward a window-ed paginated approach.
New UI
This is what our new UI looks like.
1of20of1000|'Prev'<>'Next'
The components we need to get this to work are on top of relay's spec
total count
current page offset
Relay-style spec states that the graphql response looks like:
Additionally, hasPrevious can be set to false if first or after parameters are set if it's too expensive to calculate. (Inversely, hasNext can be set to false if last or before parameters are set if it's too expensive to calculate). Formally, this looks like the following (verbatim from the spec):
hasPreviousPage is used to indicate whether more edges
exist prior to the set defined by the clients arguments. If the client
is paginating with last/before, then the server must return true if prior edges exist, otherwise false. If the client is paginating with first/after, then the client may return true if edges prior to after exist, if it can do so efficiently, otherwise may return false.
Our existing systems implemented infinite-scroll so the relay pagination and so they only had hasNextfield implemented. The values for hasPrevious was stubbed to falsesince it wasn't used.
Schema changes
To implement the new style of pagination we'll need to modify our connection schema. Relay spec says that we are allowed to modify the connection schema as long as they have edges and pageInfo:
Connection types must have fields named edges and pageInfo. They may have additional fields related to the connection, as the schema designer sees fit.
Here are the changes we are going to implement to get the desired result:
Other options I had considered was adding an index field to edges (hard to get this value because it's not trivial to compute)
We are going to add fields countwhich will be the total count of all the nodes in the query, and offset which is the count of nodes after the page that's requested. These two fields also allow us to more accurately calculate hasPrevious (for forward pagination) and hasNext (for backward pagination). Here's how we'll calculate this value:
hasPrevious = offset > 0
The relay connection code will look something like this:
typeRelayConnection<Node,Cursor>={edges: Node[];pageInfo: {startCursor: Cursor|null;endCursor: Cursor|null;hasNext: boolean;hasPrevious: boolean;}totalCount: number;edgeRange?: {start: number;end: number;};}constgetRelayConnection<Node,Cursor>=async({
first,
after,
before,
last
}: args): RelayConnection<Node,Cursor>=>{// Direction here is 'forward' or 'backward'constpaginationArgs=getPaginationArgs(after,before,first,last);letedges=paginator.edges(paginationArgs);consttotalCount=paginator.count(paginationArgs);constremainingCount=paginator.countRemaining(paginationArgs);const{ direction }=paginationArgs;consthasNext=direction==='forward' ? remainingCount>edges.length : remainingCount<totalCount;consthasPrevious=direction==='backward' ? remainingCount>edges.length : remainingCount<totalCount;conststartOfPage=direction==='forward' ? totalCount-remainingCount : remainingCount-edges.length;constendOfPage=(direction==='forward' ? startOfPage+edges.length : remainingCount)-1;return{
edges,pageInfo: {
hasNext,
hasPrevious,startCursor: edges[0]?.cursor,endCursor: edges[edges.length-1]?.cursor},count: totalCount,edgeRange: {start: startOfPage,end: endOfPage},}}
Here's an example that implements both inifinite scroll pagination and windowed pagination with relay using this method. Source code can be found here.
The text was updated successfully, but these errors were encountered:
excerpt: 'How we changed our pagination from infinite scroll to windowed pagination using relay.'
date: '2021-12-11T12:15:00-0800Z'
Windowed Pagination with Relay
Relay-style pagination is a popular option for paginating graphql queries. It differs from offset-style pagination in the sense that it uses cursors to keep track of the start and end of pages as opposed to offsets. This allows you to begin pagination at a particular node as opposed to a particular offset. There are a number of tradeoffs between the two styles of pagination. More details can be found here.
As mentioned in the article above, relay style pagination is uni-directional by design. This means that it's especially apt to infinite-scroll UI, but not so much for classical windowed-style pagination. That's not to say that that's not possible.
Problem
Our systems were initially built with infinite-scroll, but over time we realized that infinite scroll did not make sense for our systems. Infinite scroll UI is more suitable for data that's changing quickly, and less suitable for tools where you may want to pick-up where you left off, and have the page be bookmarkable. Additionally, we are not a social media app, and we are not optimizing our data for discoverability, but rather for organization (sorting, filtering, etc). For this, we wanted to move toward a window-ed paginated approach.
New UI
This is what our new UI looks like.
The components we need to get this to work are on top of relay's spec
Relay-style spec states that the graphql response looks like:
Additionally,
hasPrevious
can be set tofalse
iffirst
orafter
parameters are set if it's too expensive to calculate. (Inversely,hasNext
can be set tofalse
iflast
orbefore
parameters are set if it's too expensive to calculate). Formally, this looks like the following (verbatim from the spec):Our existing systems implemented infinite-scroll so the relay pagination and so they only had
hasNext
field implemented. The values forhasPrevious
was stubbed tofalse
since it wasn't used.Schema changes
To implement the new style of pagination we'll need to modify our connection schema. Relay spec says that we are allowed to modify the connection schema as long as they have
edges
andpageInfo
:Here are the changes we are going to implement to get the desired result:
Other options I had considered was adding an
index
field to edges (hard to get this value because it's not trivial to compute)We are going to add fields
count
which will be the total count of all the nodes in the query, andoffset
which is the count of nodes after the page that's requested. These two fields also allow us to more accurately calculatehasPrevious
(for forward pagination) andhasNext
(for backward pagination). Here's how we'll calculate this value:hasPrevious
=offset
> 0The relay connection code will look something like this:
Here's an example that implements both inifinite scroll pagination and windowed pagination with relay using this method. Source code can be found here.
The text was updated successfully, but these errors were encountered: