Last active
June 23, 2021 16:13
-
-
Save tspecht/f4b53e34a2ccf1bdef2c8fb9a475ca05 to your computer and use it in GitHub Desktop.
Bi-directional Relay-style cursor-based pagination example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}}>← 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 →</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