Apollo-client: Pagination not working

Created on 5 Aug 2020  ·  3Comments  ·  Source: apollographql/apollo-client


Hi. I tried using concatPagination, offsetLimitPagination and relayStylePagination to add pagination to my testing/learning (as I am still learning GraphQL) project following this tutorial. But it is not workin.

Intended outcome:

I can successfully paginate my results.

Actual outcome:

All caused an error. There is no documentation on how to use each method properly so maybe I am just using it wrong.

How to reproduce the issue:

Here is my sample code (you can freely changed concatPagination with offsetLimitPagination or relayStylePagination but it will still have an error) :

index.tsx

import React from "react";
import ReactDOM from "react-dom";
import {
  ApolloClient,
  InMemoryCache,
  NormalizedCacheObject,
  ApolloProvider,
} from "@apollo/client";
import { concatPagination } from "@apollo/client/utilities";

import Pages from "./pages";
import injectStyles from "./styles";

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        launches: concatPagination(),
      },
    },
  },
});

const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
  uri: "http://localhost:4000/",
  cache,
});

injectStyles();
ReactDOM.render(
  <ApolloProvider client={client}>
    <Pages />
  </ApolloProvider>,
  document.getElementById("root")
);

launches.tsx

import React, { Fragment } from "react";
import { gql, useQuery } from "@apollo/client";

import { LaunchTile, Header, Button, Loading } from "../components";
import { RouteComponentProps } from "react-router-dom";
import * as GetLaunchListTypes from "./__generated__/GetLaunchList";

export const LAUNCH_TILE_DATA = gql`
  fragment LaunchTile on Launch {
    __typename
    id
    isBooked
    rocket {
      id
      name
    }
    mission {
      name
      missionPatch
    }
  }
`;

const GET_LAUNCHES = gql`
  query launchList($after: String) {
    launches(after: $after) {
      cursor
      hasMore
      launches {
        ...LaunchTile
      }
    }
  }
  ${LAUNCH_TILE_DATA}
`;

interface LaunchesProps extends RouteComponentProps {}

const Launches: React.FC<LaunchesProps> = () => {
  const { data, loading, error, fetchMore } = useQuery<
    GetLaunchListTypes.GetLaunchList,
    GetLaunchListTypes.GetLaunchListVariables
  >(GET_LAUNCHES);

  if (loading) return <Loading />;
  if (error) return <p>ERROR</p>;
  if (!data) return <p>Not found</p>;

  return (
    <Fragment>
      <Header />
      {data.launches &&
        data.launches.launches &&
        data.launches.launches.map((launch: any) => (
          <LaunchTile key={launch.id} launch={launch} />
        ))}
      {data.launches && data.launches.hasMore && (
        <Button
          onClick={async () =>
            await fetchMore({
              variables: {
                after: data.launches.cursor,
              },
            })
          }
        >
          Load More
        </Button>
      )}
    </Fragment>
  );
};

export default Launches;

Versions

System:
    OS: macOS 10.15.3
  Binaries:
    Node: 10.19.0 - ~/.nvm/versions/node/v10.19.0/bin/node
    Yarn: 1.22.4 - ~/Work-related-tutorials/fullstack-tutorial/start/client/node_modules/.bin/yarn
    npm: 6.14.7 - ~/.nvm/versions/node/v10.19.0/bin/npm
  Browsers:
    Chrome: 84.0.4147.105
    Firefox: 70.0.1
    Safari: 13.0.5
  npmPackages:
    @apollo/client: ^3.1.2 => 3.1.2 
    apollo: ^2.30.2 => 2.30.2 

Most helpful comment

@benjamn I created a new feature request. I think that is the proper channel to discuss this if you will agree according to the contributing guide. Thanks.

All 3 comments

I can fix the pagination with a custom one like this one:

import React from "react";
import ReactDOM from "react-dom";
import {
  ApolloClient,
  InMemoryCache,
  NormalizedCacheObject,
  ApolloProvider,
} from "@apollo/client";
import { concat, getOr, isNil } from "lodash/fp";

import Pages from "./pages";
import injectStyles from "./styles";

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        launches: {
          keyArgs: false,
          merge: function (existing, incoming) {
            if (isNil(existing)) {
              return incoming;
            } else {
              const existingLaunches = getOr([], "launches", existing);
              const incomingLaunches = getOr([], "launches", incoming);
              const concatenatedLaunches = concat(
                existingLaunches,
                incomingLaunches
              );
              return { ...incoming, launches: concatenatedLaunches };
            }
          },
        },
      },
    },
  },
});

const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
  uri: "http://localhost:4000/",
  cache,
});

injectStyles();
ReactDOM.render(
  <ApolloProvider client={client}>
    <Pages />
  </ApolloProvider>,
  document.getElementById("root")
);

But I think concatPagination needs to be revamp. Can we do it with the code above in mind? Thank you.

Those helpers are not meant to cover all possible pagination use cases, so much as to demonstrate how some common use cases can be implemented using field policies.

You're more than welcome to copy and adapt them to suit your needs. In particular, the concatPagination helper assumes the top-level value of the field is an array, so you're definitely going to need to implement your own version if the array is nested inside an object that has other properties that you want to maintain.

I'm open to considering new helper functions that implement common patterns in a general way, but of course the name of the array field would have be configurable, since we're not going to hard-code launches in a generic helper function.

@benjamn I created a new feature request. I think that is the proper channel to discuss this if you will agree according to the contributing guide. Thanks.

Was this page helpful?
0 / 5 - 0 ratings