score:4

Accepted answer

what i understand from that spec is that relay can only paginate with single direction, forwards or backwards.

correct. the original design was exactly that, an infinite scroll pagination. it was never intended to be used for bi-directional pagination.

but how about sequential pages like stackoverflow questions page?

this is quite tricky do to with the default implementation of relay as it stands today because, as stated above, it was't designed for bi-directional navigation.

that said, you can still achieve this, though not through optimal means.


method 1 - passing page info through relay

this is probably the easiest method to implement, though the less optimal one. it involves passing the page info to relay as a graphql argument. this requires a new relay query each time you navigate to a new page.

example

assume you have a view displaying a list of members. you fetch a list of member nodes with memberlist. if you only want to display 10 members per "page" and we're on page 3, you could do:

app: () => relay.ql`
  fragment on app {
    memberlist(page: 3, limit: 10) {
      ...
    }
  }`
}

in your backend your sql query would now look something like:

select id, username, email from members limit 20, 10;

where 20 is the starting record: (page-1)*10 and 10 is the limit (how many items after the start). so the above will fetch the records 21-30.

note that you would need initial values for page and limit. so your final query would be:

preparevariables() {
  return {
    page: 1,
    limit: 10,
  };
},
fragments: {
  app: () => relay.ql`
    fragment on app {
      memberlist(page: $page, limit: $limit) {
        ...
      }
    }`
  }
}

and when navigating to a new page we need to update the values:

_gotopage = (pg) => {
  this.props.relay.setvariables({
    page: pg,
  });
}

method 2 - manually managing cursors

this is a slightly hacky implementation in that it essentially gives you a bi-directional navigation, even though is not natively supported. this approach is slightly more optimal to the first one, but sacrifices the ability to jump to a page directly.

note that we cannot use both first and last:

including a value for both first and last is strongly discouraged, as it is likely to lead to confusing queries and results.

hence we would construct our query normally. something like:

app: () => relay.ql`
  fragment on app {
    memberlist(first: 10, after: $cursor) {
      ...
    }
  }`
}

where the initial value for $cursor is null.

as you navigate to the next page and set the value for $cursor to be the cursor of the last member node, you also save the value in a local stack variable.

this way, whenever you navigate back you would simply pop the last cursor from the stack and use that for your after argument. it requires some extra logic in your application, but this is definitely doable.

score:1

based on method 2 given by @chris,

this is my solution in vuejs, it's roughly the same for reactjs as well.

    loadpage(type) {
      const { endcursor } = this.pageinfo
      let cursor = null 
      if(type === 'previous') {
        this.cursorstack = this.cursorstack.slice(0, this.cursorstack.length - 1)
        cursor = this.cursorstack[this.cursorstack.length - 1]
      }
      else {
        cursor = endcursor
        this.cursorstack = [...this.cursorstack, cursor]
      }
      this.fetchmore({
        variables: { cursor: cursor },
        updatequery: (previousresult, { fetchmoreresult }) => {
          if (!fetchmoreresult) return previousresult
          return fetchmoreresult
        }
      })
    },

in template:

<button :disabled="!cursorstack.length" @click="loadpage('previous')" />
<button :disabled="!pageinfo.hasnextpage" @click="loadpage('next')" />

Related Query

More Query from same tag