Paginating data within an expensive query

When render a page, we have several fields fetched from different backend sources, and a paginated list of data.

We want to include everything in one logical query, but when fetching more pages for the paginated section, we don’t want to fetch all the initial static fields again.

E.g.:

query myData {
  account {
    expensiveField1
    expensiveField2
    list(first: 20, cursor: "") {
      ...ListItem
    }
}

My first instinct was that we could create 2 separate queries, one that fires initially with all the data, and a second one which only includes the paginated field. Or alternatively could we make use of the @include directive any time we’re fetching after the initial request?

FWIW, this is exactly what we recommend in Apollo iOS. We have a pagination library that is actually set up for this use case. You provide an initial query and another query for additional pages, along with a transform function that merges the values from the pages together.

1 Like

Good to know, thanks. Is there a comparable helper for Apollo web?

You can pass a different query into the fetchMore options.

What is the difference between fetchMore using a different query and lazyQuery?

I have resolved this trouble using this approach:

export const UserDetail_QueryFragment = graphql(`
  fragment UserDetail_QueryFragment on Query {
    user(id: $id) @include(if: $includeUser) {
      id
      name
      username
      createdAt
    }
  }
`);
const ProfileScreen_Query = graphql(`
  query ProfileScreen(
    $id: ID!
    $dataFeed: FeedPostInput!
    $includeUser: Boolean = true
  ) {
    ...UserDetail_QueryFragment
    ...ProfileDetailListPost_QueryFragment
  }
`);
const handleFetchMore = () => {
    const feed = useFragment(ProfileDetailListPost_QueryFragment, data);

    if (!feed?.feed.pageInfo.hasNextPage) {
      return;
    }

    fetchMore({
      variables: {
        id,
        dataFeed: {
          where: {
            authorId: {
              equals: id,
            },
          },
          connection: {
            first: 10,
            after: feed?.feed.pageInfo.endCursor,
          },
          order: {
            createdAt: OrderBy.Desc,
          },
        },
        includeUser: false,
      },
      updateQuery(prevQueryResult, { fetchMoreResult }) {
        const prevUser = useFragment(UserDetail_QueryFragment, prevQueryResult);
        const prev = useFragment(
          ProfileDetailListPost_QueryFragment,
          prevQueryResult
        );
        const moreResult = useFragment(
          ProfileDetailListPost_QueryFragment,
          fetchMoreResult
        );

        moreResult.feed.edges = [...prev.feed.edges, ...moreResult.feed.edges];

        return {
          ...prevUser,
          ...moreResult,
        };
      },
    });

Notice, I needed to return …prevUser because fetchMore expects the same fields structure, otherwise it will throw “Unterminated String” error.

Probably if query inside fetchMore is different that initial, you don’t need this “workaround”