Rendering Long Lists

You’ll often run into the situation where you want to render a long list of data. It’s also common for that list to be much too long to query or even render on the screen. In order to address this, most APIs will accept a set of arguments on the field designed to window the list and leave it up to the client to keep a running total if the situation calls for it.

As GraphQL has matured, these arguments have somewhat standardized and fall roughly into two categories: cursor-based pagination and offset-based pagination. Houdini supports both but since our API relies on cursor-based pagination that’s what we’re going to show here. If you want to read more about pagination, head over to the pagination guide.

Paginated Queries

Interacting with a paginated query is pretty similar to everything we’ve been doing so far. Just use the graphql tag and decorate the paginated field with the @paginate directive. Go ahead and change the query with the SpeciesInfo document to look like this:

<script>
	import { graphql } from '$houdini'

	const speciesInfo = graphql`
		query SpeciesInfo($id: Int!) {
			species(id: $id) {
				name
				flavor_text
				favorite
				evolution_chain {
					...SpeciesPreview
				}
				moves(first: 1) @paginate {
					edges {
						node {
							...MoveDisplay
						}
					}
				}
				...SpriteInfo
			}
		}
	`
</script>

At the surface, a paginated query store is basically the same thing as a normal query except for a few extra utilities. They’re pretty self-explanatory but just in case there’s any confusion: there’s a pageInfo value on the store that contains to an object with meta data about the current page and loadNextPage is an async function that will load the next page and append the result of the field tagged with @paginate to the existing value in our cache. For a more in-depth summary of what you can do with @paginate, you can check out the Pagination Guide.

It’s time to add some visuals. Add an import for the MoveDisplay component and copy the following block as the second child in the right panel (between div#species-evolution-chain and the nav):

<MoveDisplay move={$speciesInfo.data.species.moves.edges[0].node} />

In most situations houdini’s automatic appending is great since it covers popular patterns like infinite scrolling or any kind of list that just keeps growing. Unfortunately, we are only showing one move at a time so we can’t just display all of the information we’re holding onto in memory. Also, the user might want to step back in the list and we won’t need to load the next or previous page if we already have it loaded. In order to address this, we need hold onto a bit of local state that will track the index of the move on display. When we load the next move we will first check if we already have the element loaded and if not, we will trigger loadNextPage to grab it and then update our state to display the new result.

<script>
	import { navigating } from '$app/stores'
	import { UpButton, DownButton } from '~/components'

	let moveIndex = 0
	$: hasPrevMove = moveIndex > 0
	$: hasNextMove = moveIndex < $speciesInfo.data.species.moves.edges.length - 1 || $pageInfo.hasNextPage

	const loadNextMove = async () => {
		// if we haven't already seen this page
		if (!$speciesInfo.data.species.moves.edges[moveIndex + 1] && $pageInfo.hasNextPage) {
			// load the next page of data
			await speciesInfo.loadNextPage()
		}

		// its safe to increment the move index
		moveIndex += 1
	}

	const loadPrevMove = () => {
		moveIndex--
	}

	// when we navigate to another species, reset the move index
	navigating.subscribe(() => {
		moveIndex = 0
	})
</script>

<!-- Use this instead of the move display we added earlier -->
<div id="move-summary">
	<MoveDisplay move={$speciesInfo.data.species.moves.edges[moveIndex].node} />
	<div id="move-controls">
		<UpButton disabled={!hasPrevMove} on:click={speciesInfo.loadPrevMove} />
		<DownButton disabled={!hasNextMove} on:click={speciesInfo.loadNextMove} />
	</div>
</div>

You can now verify that it all works by opening the network tab and clicking on the up and down arrows. You should only see network requests being sent when loading a move we haven’t seen before.

Admittedly this example is a little bit complicated for a first-time introduction into pagination but hopefully it illustrates just how much we can do with houdini’s API. Most of the time, you will just be able to use loadNextPage and pageInfo directly without all of the extra state management.

That’s it!

This is the last topic we wanted to cover as part of the guide! Thank you so much for getting all the way through - we really appreciate the dedication. If there were any sections that were confusing, or changes you think would be helpful, please open up a discussion on GitHub.

If you want to read more about what Houdini can do, we recommending checking out the Working with GraphQL guide next.