Next.js & Apollo GraphQL Performance Tuning: Lists & pagination

October 08, 2018 0 Comments

Next.js & Apollo GraphQL Performance Tuning: Lists & pagination

 

 

In a previous post I’ve introduced Next.js and GraphQL a bit, building a small site with data coming from the Star Wars REST API behind a GraphQL server. After that, it’s time to dive into more technical details and check out some common use-cases, where we can benefit from how Next.js and GraphQL work together.

This post is about creating lists and paginating them using data from GraphQL, and relying on React components within Next.js. Heads up, this article might be a bit code-heavy!

The task is to load a list of items, and implement pagination. The plan is to use the same GraphQL Query over and over again, but update its parameters for every new page request. So let’s get the first page, and render it:

 

export

const

getStarships

=

gql

` query getStarships { starshipList { items { name id } } } `

const

List

=

(

props

)

=

>

(

<div>

<ul>

{

props

.

data

.

starshipList

.

items

.

map

(

item

=

>

(

<li key={item.id}>

{

item

.

name

}

</li>

)

)

}

</ul>

</div>

)

export

default

graphql

(

getStarships

)

(

List

)

Simple, isn’t it? Let’s see what happens above in details:

  • parse our GraphQL query using gql
    this handy tag for template literals creates graphql query structures from a string for the graphql client
  • define our query
    fetch the starshipList from the schema, but only two fields from every starship, name & id
  • make a listing component
    the result from the graphql query will be in the props.data
  • finally, compose this component with the graphql HOC

The result object from the query has several handy methods and properties, like the loading state, errors or the variables of the latest query - and the actual data, following the structure of the Schema, even the Type we’ve just received. This is a kind of tight coupling between the iterator, that will render the list and the data Schema.

Having connections like this between data and rendering logic should be avoided. The component that renders data can be a shared component, which should not care about the Schema Type (in this case: “starshipList”). On the other side, the Schema might change in the future, and after that, you have to refactor all your components that rely on Schema Types in query results. So let’s loosen up that coupling a bit.

You can define a map function for the query which allows you to compute new props for your component after the query received results but before rendering. Play with that a bit, and include only the actual data from the query, and the loading state in the props of our listing component:

 

export

const

getStarships

=

gql

` query getStarships { starshipList { items { name id } } } `

const

List

=

(

props

)

=

>

(

<div>

<ul>

{

props

.

data

.

map

(

item

=

>

(

<li key={item.id}>

{

item

.

name

}

</li>

)

)

}

</ul>

</div>

)

export

default

graphql

(

getStarships

,

{

props

:

(

{

data

,

ownProps

}

)

=

>

{

const

{

starshipPages

,

loading

}

=

data

const

newProps

=

{

loading

,

data

:

starshipPages

.

items

}

return

Object

.

assign

(

{

}

,

ownProps

,

newProps

)

}

}

)

(

List

)

Now, the List component is free of any reference and coupling with the GraphQL Schema, and we will be able to properly re-use it!

Among the methods in the result object, there is one particular, that we will use here, called fetchMore. This method is designed to do pagination with GraphQL. It takes a single options object, where you can put the updated query, or just the new parameters, and a function (required) - that will handle the result, and update the data you already have.

Let’s prepare our code for pagination:

  • add the page parameter to the query
  • set the option notifyOnNetworkStatusChange true, so we’ll receive updates on loading state automatically
  • set the default page to the first page
  • pass in the actual page number to props
  • implement a function to fetch the next page using fetchMore
 

export

const

getStarships

=

gql

` query getStarships($page: Int = 1) { starshipList(page: $page) { page items { name id } } } `

const

List

=

(

props

)

=

>

(

<div>

{

props

.

loading

?

'LOADING'

:

''

}

<ul>

{

props

.

data

.

map

(

item

=

>

(

<li key={item.id}>

{

item

.

name

}

</li>

)

)

}

</ul>

<button onClick={e => props.loadPage(props.page + 1) }>

Load Page

{

props

.

page

+

1

}

</button>

</div>

)

export

default

graphql

(

getStarships

,

{

options

:

{

notifyOnNetworkStatusChange

:

true

,

variables

:

{

page

:

1

}

,

}

,

props

:

(

{

data

,

ownProps

}

)

=

>

{

const

{

fetchMore

,

starshipPages

:

{

items

,

page

}

,

loading

}

=

data

const

newProps

=

{

loading

,

page

,

data

:

items

,

loadPage

:

(

nextPage

)

=

>

{

return

fetchMore

(

{

variables

:

{

page

:

nextPage

}

,

updateQuery

:

(

prev

,

{

variables

,

fetchMoreResult

}

)

=

>

{

if

(

!

fetchMoreResult

)

return

prev

;

return

Object

.

assign

(

{

}

,

fetchMoreResult

,

{

variables

}

)

}

}

)

}

}

return

Object

.

assign

(

{

}

,

ownProps

,

newProps

)

}

}

)

(

List

)

Now let’s see what happens if the user clicks the “Load Page” button:

  • page is accessed from props, incremented and passed to the function
  • loadPage calls fetchMore,
    • with the new page value in variables,
    • updateQuery to handle results


  • the query returns with values

  • inside updateQuery you can append the results to the ones you already have, or replace them

  • returning data from here calls the props method on the Query again

  • the props method calculates new props,

  • the component renders with the next page


I’ve visualized this on a few slides, see them here:

In a following post I’ll show a way to make quick page transitions, from a list of items to a detailed item view, utilizing the goodies in Next.js and the cache in Apollo Client.


Tag cloud