Version: 1.1.0

@PaginationFragment

The @PaginationFragment property wrapper is very similar to a @Fragment, but it supports loading additional items upon request. When using a pagination fragment, you don't have to load an entire list of data all at once. You can control when you load more, and you'll be able to easily update your UI accordingly.

A @PaginationFragment expects your schema to be structured in a specific way to support paging. See the GraphQL Cursor Connections Specification for more details.

Example#

import SwiftUI
import RelaySwiftUI
private let userFragment = graphql("""
fragment ToDoList_user on User
@refetchable(queryName: "ToDoListPaginationQuery")
@argumentDefinitions(
count: { type: "Int", defaultValue: 10 }
cursor: { type: "Cursor" }
) {
todos(first: $count, after: $cursor)
@connection(key: "ToDoList_todos") {
edges {
node {
id
...ToDoItem_todo
}
}
}
}
""")
struct ToDoList: View {
@PaginationFragment<ToDoList_user> var user
var body: some View {
if let user = user {
List {
ForEach(user.todos ?? []) { todo in
ToDoItem(todo: todo)
}
if user.isLoadingNext {
Text("Loading…").foregroundColor(.secondary)
} else if user.hasNext {
Button("Load more tweets…") {
user.loadNext(10)
}
}
}
}
}
}

Requirements#

There are a few special requirements for the fragment in order for it to be valid to use with a @PaginationFragment:

  • The fragment must have a @refetchable directive that names a query operation that will be generated to fetch additional data for the fragment. Using @refetchable requires that the fragment be declared on Query, Viewer, or a type that implements the Node. Otherwise, Relay won't know where in the graph to fetch the new data from.
  • A field in the fragment must have a @connection directive that marks the connection field to use for paging. See the GraphQL Cursor Connections Specification for more about connections.

Parameters#

  • F: A type parameter (surrounded in <>) for the type of the fragment to use. The type will be generated by the Relay compiler with a name matching the fragment name in the GraphQL snippet.

Property value#

The @PaginationFragment property will be a read-only optional value with the fields the fragment requests. This value will automatically update and re-render the view when more items are loaded or when the Relay store updates any relevant records.

The value will also include some additional properties related to paging and refetching:

  • isLoadingNext: Bool: Indicates if there is an in-flight request to load more items from the end of the list.
  • isLoadingPrevious: Bool: Indicates if there is an in-flight request to load more items from the beginning of the list.
  • hasNext: Bool: Indicates if there are more items that can be fetched from the end of the list.
  • hasPrevious: Bool: Indicates if there are more items that can be fetched from the beginning of the list.
  • loadNext(_ count: Int): Function that can be called to fetch the next count items from the end of the list.
  • loadPrevious(_ count: Int): Function that can be called to fetch the previous count items from the beginning of the list.
  • refetch(_ variables: Variables? = nil): Function that can be called to trigger a refetch of the fragment's data. variables will be the variables for the refetch query that Relay generates for you. This may change which node the fragment is targetting from then on. That's okay: Relay will keep track of that for you, but be aware that it may not match the original fragment your view is passing in.

Using @connection fields as Collections#

When generating the data types for a pagination fragment, the Relay compiler can make it easier to use your connection by making it usable as a Swift Collection. For example, here are the types generated for the above example ToDoList_user fragment:

extension ToDoList_user {
public struct Data: Decodable {
public var todos: TodoConnection_todos?
public var id: String?
public struct TodoConnection_todos: Decodable, ConnectionCollection {
public var edges: [TodoEdge_edges?]?
public struct TodoEdge_edges: Decodable, ConnectionEdge {
public var node: Todo_node?
public struct Todo_node: Decodable, Identifiable, ToDoItem_todo_Key, ConnectionNode {
public var id: String
public var fragment_ToDoItem_todo: FragmentPointer
}
}
}
}
}

The todos field is the @connection field, and has type TodoConnection_todos. The TodoConnection_todos conforms to ConnectionCollection, which makes it usable as a Collection. In general, you can use the todos field as though it were an array, including in a SwiftUI List or ForEach.

This conformance is only generated when your fragment includes the following structure:

fragment MyFragment_foo {
someItems @connection(key: ...) {
edges {
node {
...
}
}
}
}

In other words, your fragment must have a field with the @connection directive. That field must have an edges field selected, and the edges field must have a node field selected. If those conditions are met, the Relay compiler will make the types for those fields conform to the necessary protocols to be able to use the connection field as a Collection.

In practice, this means that referring to your paged items in your view is much nicer. Instead of this:

List(user.todos?.edges?.compactMap { $0?.node } ?? []) { ... }

You can just write:

List(user.todos ?? []) { ... }

This gives you the benefits of using GraphQL cursors for paging, but without burdening your view code with worrying about edges and their nodes.