Pagination comes in many different flavors depending on the desired user experience and the shape of the underlying API. APIs such as ’s implement the to standardize pagination and slicing of large result sets. This approach is well-suited to infinite-scrolling, but can also be used for “windowed” paging with next/previous page buttons. GraphQL GitHub Relay Cursor Connections Specification Cursor connections work by passing in one of the following query argument pairs: Forward Windowed Pagination is a positive, non-zero integer describing the maximum number of results to return from the leading side of the results set. During backward-pagination, this value must be null. first is an opaque cursor type value provided by the field of the connection’s object. For the first page, this value will be null. During backward-pagination, this value must be null. after endCursor [pageInfo](https://developer.github.com/early-access/graphql/object/pageinfo/) Backward Windowed Pagination is a positive, non-zero integer describing the maximum number of results to return from the trailing side of the results set. During forward-pagination, this value must be null. last is an opaque cursor type value provided by the field of the connection’s object. During forward-pagination, this value must be null. before startCursor [pageInfo](https://developer.github.com/early-access/graphql/object/pageinfo/) GraphQL Query fragment example Relay.QL`fragment on Query {search(query: $q, type: REPOSITORY,first: $first, after: $after,last: $last, before: $before) {repositoryCountpageInfo {startCursorendCursor}edges {node {... on Repository {idnameurl}}}}}` The field is used to calculate the total number of result pages. is used to populate the / cursor arguments. contains the search results inside the Union type. We have restricted the results to only contain type nodes using the argument. repositoryCount [pageInfo](https://developer.github.com/early-access/graphql/object/pageinfo/) before after [edges](https://developer.github.com/early-access/graphql/object/searchresultitemedge) [node](https://developer.github.com/early-access/graphql/union/searchresultitem/) [repository](https://developer.github.com/early-access/graphql/object/repository/) type Using the data from this query, the application can calculate the total number of pages and keep track of which page we are currently on. The page count is the rounded-up result of dividing the total number of results by the number of results per page. To see the next page, run the query using the field of the current page as the argument and the page size as the argument. To go back a page, run the query using the field of the current page as the argument and the page size as the argument. endCursor after first startCursor before last Encapsulating pagination logic in a React Component class Search extends Component { In order to propagate the query results through a , we need to create a to process, act on, and display the data. Relay Container React Component constructor(props) {super(props); this.state = { q: "", pageSize: 25, pageCursor: 1, pageCount: 1 }; this.handleQueryChange = this.handleQueryChange.bind(this); this.handleSearch = this.handleSearch.bind(this); this.handlePrevPage = this.handlePrevPage.bind(this); this.handleNextPage = this.handleNextPage.bind(this); } The is mostly boilerplate with some initial state and function bindings to ensure the component is the function context during callbacks. constructor handleQueryChange(e) {this.setState({q: e.target.value});} is called by an <input> callback to update the search query based on user input. The actual search does not happen until the form is submitted. handleQueryChange onChange handleSearch(e) {this.props.relay.setVariables({q: this.state.q,first: this.state.pageSize,after: null,last: null,before: null});this.isNewSearchQuery = true;e && e.preventDefault(); // Stop form action} is called by the <form> callback to perform the search by updating the variables in the Relay query. The flag is used by as a cue to reset the page number and page count when the query value changes. handleSearch onSubmit isNewSearchQuery componentWillReceiveProps componentWillReceiveProps(nextProps) {if (this.isNewSearchQuery) {let repoCount = nextProps.query.search.repositoryCount;let reposPerPage = this.state.pageSizethis.setState({pageCursor: 1,pageCount: Math.ceil(repoCount / reposPerPage)});this.isNewSearchQuery = false;}} is a React Component hook that is called when the query results are returned. From here, we can check if the flag was set and reset the page cursor to the beginning and re-calculate the page count. This pattern works best when the total number of results does not wildly change during pagination. [componentWillReceiveProps](https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops) isNewSearchQuery handlePrevPage() {if (this.state.pageCursor > 1) {let pageCursor = this.state.pageCursor - 1;this.props.relay.setVariables({last: this.state.pageSize,before: this.props.query.search.pageInfo.startCursor,first: null,after: null}, ({ ready, done }) => {if (ready && done) {this.setState({ pageCursor });}});}} handleNextPage() {if (this.state.pageCursor < this.state.pageCount) {let pageCursor = this.state.pageCursor + 1;this.props.relay.setVariables({first: this.state.pageSize,after: this.props.query.search.pageInfo.endCursor,last: null,before: null}, ({ ready, done }) => {if (ready && done) {this.setState({ pageCursor });}});}} and perform bounds checking based on the pagination state, then update the Relay query variables using the current results’ . The actual page number does not update until the new query has completed by utilizing the optional argument of . handlePrevPage handleNextPage [pageInfo](https://developer.github.com/early-access/graphql/object/pageinfo/) onReadyStateChange [setVariables](https://facebook.github.io/relay/docs/api-reference-relay-container.html#setvariables) render() {let { search } = this.props.query;let { pageCursor, pageCount } = this.state;let isPrevPageDisabled = (pageCursor === 1);let isNextPageDisabled = (pageCursor === pageCount);return (<div><h2>Search</h2> <form onSubmit={this.handleSearch}> <input placeholder="Repository name" type="text" value={this.state.q} onChange={this.handleQueryChange} /> &nbsp; <button type="submit">Search</button> </form> <h3>{search.repositoryCount} results</h3> <button onClick={this.handlePrevPage} disabled={isPrevPageDisabled}>Previous Page</button> &nbsp; Page {pageCursor} of {pageCount} &nbsp; <button onClick={this.handleNextPage} disabled={isNextPageDisabled}>Next Page</button> <ul className="Search-results"> {search.edges.map(({ node }, index) => ( <SearchResult key={node.id} repository={node} /> ))} </ul> </div> ); }} creates the DOM for the Search component, hooking up all of the callbacks and displaying the results by mapping through the and encapsulating the search result items in their own component: render [edges](https://developer.github.com/early-access/graphql/object/searchresultitemedge) class SearchResult extends Component {render() {let repo = this.props.repository;let stargazersUrl = repo.url + "/stargazers";return (<li><h5 className="stargazers"><a href={stargazersUrl}>★ {repo.stargazers.totalCount}</a></h5><h4 className="headline"><a href={repo.owner.url}>{repo.owner.login}</a>/<a href={repo.url}>{repo.name}</a></h4><span className="description">{repo.description}</span></li>)}} Wrapping it all up with a Relay Container Finally, we can create a Relay Container based on our query fragments and React Component: Relay.createContainer(Search, {initialVariables: {q: "",first: null,last: null,before: null,after: null},fragments: {query: () => Relay.QL`fragment on Query {search(query: $q, type: REPOSITORY,first: $first, after: $after,last: $last, before: $before) {repositoryCountpageInfo {startCursorendCursor}edges {node {... on Repository {idnameurldescriptionstargazers {totalCount}owner {loginurl}}}}}}`,},}); This container can be passed into a or used as a child-container inside of a parent with the static method. Relay Renderer [getFragment](https://facebook.github.io/relay/docs/api-reference-relay-container.html#getfragment) I hope this was helpful for beginners trying to wrap their head around the GraphQL pagination model and integrate with React and Relay Classic. I wanted to share this because I could not find any examples of bi-directional pagination in GraphQL while solving this exercise. Thanks for reading. If you are looking for an experienced engineer, send me a message or drop me an email at JavaScript on LinkedIn jobs at christopherbonhage.com