Last updated

Pagination - React Tutorial (7/7)

Welcome to the 7th exercise in the React Track of this Apollo Client Tutorial!


The goal in this exercise is to use pagination to provide a better browsing experience:


Change to the 7th exercise, install the dependencies and run the Pokedex React App from your console

cd pokedex-react/exercise-07
yarn install # or npm install
yarn start # or npm start

Browsing the Pokedex

We want to allow the browsing of pokemons in different pages.

Preparing the pages

As we want to organize pokemons in different pages, we already added some changes to our routes in index.js:

    <ApolloProvider client={client}>
      <Router history={browserHistory}>
        <Route path='/' component={Pokedex}>
          <IndexRedirect to='/1' />
          <Route path='/:page' component={Pokedex} />
        <Route path='/view/:pokemonId' component={PokemonPage} />
        <Route path='/create/:trainerId' component={AddPokemonCard} />

At the / path, we added a new route with the parameter :page. Later we want to see the first three pokemons on the first page, the next 3 pokemons on the second page and so on. That's why we already added a bit of page logic in Pokedex.js:

  _nextPage = () => {
    this.props.router.replace(`/${ + 1}`)

  _previousPage = () => {
    this.props.router.replace(`/${ - 1}`)

  _isFirstPage = () => {
    return === '1'

  _isLastPage = () => {
    return <= * POKEMONS_PER_PAGE

We provide two methods to transition to the next or previous page, and two to check if we are on the first or last page. Note that we use the _ownedPokemonsMeta.count meta information that the server provides us with on the Trainer object. Of course, we also included it in the query itself as well:

const TrainerQuery = gql`
  query TrainerQuery($name: String!) {
    Trainer(name: $name) {
      ownedPokemons {
      _ownedPokemonsMeta {

With these preparations, you are now ready to actually implement pagination in your pokedex!

Offset-based pagination

The Apollo documentation has more information on different pagination concepts. In this exercise, we will focus on offset-based pagination. That is, we express how many pokemons we want to query, and how many we want to skip. Our server offers the first and skip parameters for the ownedPokemons field of the Trainer query, that give us exactly that functionality!

So go ahead and add the first and skip variables to the TrainerQuery in Pokedex.js like this:


copy to src/components/Pokedex.js

const TrainerQuery = gql` query TrainerQuery($name: String!, $first: Int!, $skip: Int!) { Trainer(name: $name) { id name ownedPokemons(first: $first, skip: $skip) { id name url } _ownedPokemonsMeta { count } } } `
Page 1

What values do we pass to these variables? For first, we always pass POKEMONS_PER_PAGE which is three in our case. However, the value we pass for the skip variable depends on the current page, that is accessible with the params props. We can access the props when setting query options like this:


copy to src/components/Pokedex.js

const PokedexWithData = graphql(TrainerQuery, { options: (ownProps) => ({ variables: { name: '__NAME__', skip: ( ownProps.params && && ( - 1) * POKEMONS_PER_PAGE ) || 0, first: POKEMONS_PER_PAGE, }, forceFetch: true, }) })(withRouter(Pokedex)) export default PokedexWithData
Page 1

All that you have to do now is to update render method of Pokedex.js. Before actually rendering, we make sure that the in the props is a sensible number, if not we navigate to the first page. If all is well, we include the prepared PageNavigation component in the render method of Pokedex.js to allow the user to browse through the different pages:


copy to src/components/Pokedex.js

componentWillReceiveProps({ router, data: { Trainer }, params: { page } }) { if (!Trainer) { return } const pokemonCount = Trainer._ownedPokemonsMeta.count if ((!pokemonCount && !this._isFirstPage()) || isNaN(page) || pokemonCount < (page - 1) * POKEMONS_PER_PAGE || page < 1) { router.replace('/1') } } // ... render () { if ( { return (<div>Loading</div>) } if ( { console.log( return (<div>An unexpected error occurred</div>) } return ( <div className='w-100 bg-light-gray min-vh-100'> <Title className='tc pa5'> Hey {}, there are {} Pokemons in your pokedex </Title> <div className='flex flex-wrap justify-center center w-75'> {!this._isFirstPage() && <PageNavigation onClick={this._previousPage} isPrevious={true} />} { === '1' && <AddPokemonPreview trainerId={} />} { => <PokemonPreview key={} pokemon={pokemon} /> )} {!this._isLastPage() && <PageNavigation onClick={this._nextPage} isPrevious={false} />} </div> </div> ) }
Page 1

Open http://localhost:3000 in your browser and navigate through your existing pokemons. Create and delete pokemons and check that the pagination works for different amount of pokemons.


  • We saw how we can use first and skip to realize offset-based pagination
  • When setting query variables, inner props like router params can be accessed to modify the query
Edit this page