Here's your chance to speak at GraphQL Summit in New York City, October 8 - 10, 2024! 🏙️ Submit your proposal by May 31.
Docs
Launch GraphOS Studio

Directional Pagination


GraphQLQueryPager supports pagination in both the forward and reverse direction, as well as both at once.

Forward Pagination

Forward pagination is the most common form of pagination. It is used to fetch the next n items in a list. While we have many options depending on our requirements -- whether we use one or two, whether we want to use a cursor or an offset, whether we want to transform the results, etc. -- we will examine using a cursor with a single query.

let initialQuery = MyQuery(first: 10, after: nil)
let pager = GraphQLQueryPager(
client: client,
initialQuery: initialQuery,
extractPageInfo: { data in
CursorBasedPagination.Forward(
hasNext: data.values.pageInfo.hasNextPage ?? false,
endCursor: data.values.pageInfo.endCursor
)
},
pageResolver: { page, paginationDirection in
// As we only want to support forward pagination, we can return `nil` for reverse pagination
switch paginationDirection {
case .next:
return MyQuery(first: 10, after: page.endCursor ?? .none)
case .previous:
return nil
}
}
)

Reverse Pagination

Reverse pagination is used to fetch the previous n items in a list. While we have many options depending on our requirements -- whether we use one query or two, whether we want to use a cursor or an offset, whether we want to transform the results, etc. -- we will examine using a cursor with a single query.

let initialQuery = MyQuery(first: 10, after: nil)
let pager = GraphQLQueryPager(
client: client,
initialQuery: initialQuery,
extractPageInfo: { data in
CursorBasedPagination.Reverse(
hasNext: data.values.pageInfo.hasPrevious ?? false,
endCursor: data.values.pageInfo.startCursor
)
},
pageResolver: { page, paginationDirection in
// As we only want to support reverse pagination, we can return `nil` for forward pagination
switch paginationDirection {
case .next:
return nil
case .previous:
return MyQuery(first: 10, before: page.startCursor ?? .none)
}
}
)

Bi-directional Pagination

Bi-directional pagination is used to fetch the next n items in a list, as well as the previous n items in a list. Given that we can fetch in both directions, the implication is that the initial query fetched is at neither the head nor tail of the list of results. For this example, we will examine using a cursor with a single query.

let pager = GraphQLQueryPager(
client: client,
initialQuery: MyQuery(first: 10, after: nil),
extractPageInfo: { data in
CursorBasedPagination.Bidirectional(
hasNext: data.values.pageInfo.hasNextPage ?? false,
endCursor: data.values.pageInfo.endCursor,
hasPrevious: data.values.pageInfo.hasPreviousPage ?? false,
startCursor: data.values.pageInfo.startCursor
)
},
pageResolver: { page, direction in
switch direction {
case .next:
return MyQuery(first: 10, after: page?.endCursor ?? .none)
case .previous:
return MyQuery(first: 10, before: page?.startCursor ?? .none)
}
}
)

Custom Configuration

Generally, it's recommended to use a convenience initializer to create a configured GraphQLQueryPager. However, if you need to customize the configuration, you can use the init method directly. If your application only uses a specific type of pagination, it is simple to declare a custom GraphQLQueryPager convenience initializer that can be used throughout your application. This allows you to easily instantiate a pager without worrying about cases that your application does not support. If your pagination scheme only supports forward pagination and offsets, you can declare a custom GraphQLQueryPager convenience initializer that only supports forward pagination and offsets. In this example, we declare two initializers for forward offset pagination -- one with a transform, and one without:

extension GraphQLQueryPager {
static func makeForwardOffsetQueryPager<InitialQuery: GraphQLQuery>(
client: ApolloClientProtocol,
watcherDispatchQueue: DispatchQueue = .main,
queryProvider: @escaping (OffsetPagination.Forward?) -> InitialQuery,
extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Forward
) -> GraphQLQueryPager where Model == PaginationOutput<InitialQuery, InitialQuery> {
GraphQLQueryPager.init(
client: client,
initialQuery: queryProvider(nil),
watcherDispatchQueue: watcherDispatchQueue,
extractPageInfo: pageExtraction(transform: extractPageInfo),
pageResolver: { page, direction in
guard direction == .next else { return nil }
return queryProvider(page)
}
)
}
static func makeForwardOffsetQueryPager<InitialQuery: GraphQLQuery, T>(
client: ApolloClientProtocol,
watcherDispatchQueue: DispatchQueue = .main,
queryProvider: @escaping (OffsetPagination.Forward?) -> InitialQuery,
extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Forward,
transform: @escaping (InitialQuery.Data) throws -> Model
) -> GraphQLQueryPager where Model: RangeReplaceableCollection, T == Model.Element {
GraphQLQueryPager.init(
client: client,
initialQuery: queryProvider(nil),
watcherDispatchQueue: watcherDispatchQueue,
extractPageInfo: pageExtraction(transform: extractPageInfo),
pageResolver: { page, direction in
guard direction == .next else { return nil }
return queryProvider(page)
},
initialTransform: transform,
pageTransform: transform
)
}
}
private func pageExtraction<InitialQuery: GraphQLQuery, P: PaginationInfo, T>(
transform: @escaping (InitialQuery.Data) -> P
) -> (PageExtractionData<InitialQuery, InitialQuery, T>) -> P {
{ extractionData in
switch extractionData {
case .initial(let value, _), .paginated(let value, _):
return transform(value)
}
}
}
private func pageExtraction<InitialQuery: GraphQLQuery, PaginatedQuery: GraphQLQuery, P: PaginationInfo, T>(
initialTransform: @escaping (InitialQuery.Data) -> P,
paginatedTransform: @escaping (PaginatedQuery.Data) -> P
) -> (PageExtractionData<InitialQuery, PaginatedQuery, T>) -> P {
{ extractionData in
switch extractionData {
case .initial(let value, _):
return initialTransfom(value)
case .paginated(let value, _):
return paginatedTransform(value)
}
}
}
Previous
Using Custom Response Models
Next
Multi-query Pagination
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company