Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save tspecht/f4b53e34a2ccf1bdef2c8fb9a475ca05 to your computer and use it in GitHub Desktop.
Save tspecht/f4b53e34a2ccf1bdef2c8fb9a475ca05 to your computer and use it in GitHub Desktop.
Bi-directional Relay-style cursor-based pagination example
import PropTypes from 'prop-types';
import React from 'react';
import {Pager} from 'react-bootstrap';
import gql from 'graphql-tag';
import {graphql} from 'react-apollo';
const PokemonQuery = gql`
query Pokemons($trainerID: Int!, $first: Int, $after: String) {
pokemons(trainerID: $trainerID, first: $first, after: $after) {
edges {
cursor
node {
id
name
species {
name
}
}
}
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
}
}
`;
const defaultState = {
cursorStack: [] // The list we are using to save all the cursors in stack-style
};
export class PokemonList extends React.Component {
constructor(props) {
super(props);
this.state = defaultState;
}
componentDidUpdate(prevProps) {
// If some of our props change (e.g. we got another trainer passed-in) we need to flush the cursor stack since
// we want to start paginating from the beginning
let shouldFlushStack = prevProps.trainerID != this.props.trainerID;
if (shouldFlushStack) {
this.setState({cursorStack: []});
}
}
render() {
var content = null;
const pokemons = this.props.pokemons;
if (pokemons) {
content = pokemons.edges.map(edge => <div className="pokemon-info">
Name: {edge.node.name}
<br/>
ID: {edge.node.id}
</div>
);
}
return <div className="pokemon-list">
{content}
{this.renderPager()}
</div>
}
renderPager() {
if (this.props.pokemons) {
return <Pager>
<Pager.Item previous disabled={this.state.cursorStack.length == 0} onClick={() => {
// Remove the latest lastEndCursor from the stack and then use the last one to query again.
let existingStack = this.state.cursorStack;
existingStack.pop();
this.setState({cursorStack: existingStack});
// Get the last lastEndCursor and query with it
let lastEndCursor = existingStack.length > 0 ? existingStack[existingStack.length - 1] : undefined;
this.props.loadMorePokemons(true, lastEndCursor);
}}>&larr; Previous Page</Pager.Item>
<Pager.Item next disabled={!this.props.pokemons.pageInfo.hasNextPage} onClick={() => {
// Add the last end cursor to the stack before fetching the next page so we can go back
let existingStack = this.state.cursorStack;
existingStack.push(this.props.pokemons.pageInfo.endCursor);
this.setState({cursorStack: existingStack});
// Load the next page
this.props.loadMorePokemons(false, undefined);
}}>Next Page &rarr;</Pager.Item>
</Pager>
}
return null;
}
}
PokemonList.propTypes = {
trainerID: PropTypes.number.isRequired
};
// Default page size. Change this to whatever you want or make it configurable by the user.
const pageSize = 25;
const PokemonListWithData = graphql(PokemonQuery, {
options: (props) => {
return {
trainerID: props.trainerID,
first: pageSize
};
},
props: ({ownProps, data: {loading, pokemons, fetchMore, variables}}) => {
return {
...ownProps,
loading,
pokemons,
loadMorePokemons: (isGoingBack, lastEndCursor) => {
// Make sure we are not loading in an infinite loop!
if (!pokemons.pageInfo.hasNextPage) {
return;
}
// Build up the new variables for the query.
// We are using all the existing ones and only override the after for pagination
// If we are going back we take the proper cursor from the input argument, if not we automatically take the last
// end cursor to have forward-based pagination.
var newVariables = {...variables, after: isGoingBack ? lastEndCursor : pokemons.pageInfo.endCursor};
return fetchMore({
variables: newVariables,
updateQuery: (previousResult, {fetchMoreResult}) => {
// Return the props that should be updated. Note we are replacing the whole pokemons object here.
// Alternatively we could just replace the pageInfo attribute on it and append the new edges to the
// existing ones.
return {
pokemons: fetchMoreResult
}
}
})
}
};
}
})(PokemonList);
export default PokemonListWithData;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment