Pagination cache rules in one place, not full app

I’m building an app that makes several requests to the same field (competitions) with different filtering options. Mostly they are filtered depending on start and end date to determine and display the competitions in different views depending on if they are upcoming, ongoing or completed. Also there’s a search function, calling and filtering the same field “competitions”.

On the page “All Competitions” I’d like to add offset pagination, which works fine by it’s own when added as offsetLimitPagination() as a typePolicy on the field “competitions” in the InMemoryCache.

However, this of course messes up the cache rules everywhere else where the “competitions” field is called, by merging items in the cache that doesn’t corresponds with each queries rules, but is stored under “competitions” in the cache.

It’s possible to bypass by using “no-cache” in the fetchPolicy of the other queries, but then we loose most of the benefit using Apollo Client.

How would one go about solving this issue? As it is now, either everything works but the pagination on the All Competitions-page, or the other way around. There should be a way to define different typePolicies for different pages/routes in the app.

1 Like

I’ve always envisaged this issue ever since we first started to use the relayStylePagination field policy in AC3 and now it has presented itself to us because one component needs infinite pagination and another needs separate pages, e.g.

const cache = new InMemoryCache({
  typePolicies: {
    User: {
      fields: {
        // InfinitePagination only works when we define this cache policy, but this breaks the RegularPagination component.
        posts: relayStylePagination()
      }
    }
  }
});
  

const QUERY = gql`
  query UserPosts($first: Int!, $after: String) {
    user {
      id
      posts(first: $first, after: $after) {
        pageInfo {
          endCursor
        }
        edges {
          node {
            id
          }
        }
      }
    }
  }
`;

const InfinitePagination: React.FunctionComponent<any> = () => {
  const { data, fetchMore } = useQuery(QUERY, { variables: { first: 2 } });
  return (
    <div style={{ color: 'limegreen' }}>
      <pre>{JSON.stringify(data, null, 2)}</pre>
      <button
        onClick={() => {
          fetchMore({
            variables: {
              after: data.user.posts.pageInfo.endCursor
            }
          });
        }}
      >
        Load more
      </button>
    </div>
  );
};

const RegularPagination: React.FunctionComponent<any> = () => {
  const [after, setAfter] = useState();
  const { data, previousData } = useQuery(QUERY, {
    variables: { first: 2, after }
  });
  return (
    <div style={{ color: 'lightblue' }}>
      <pre>{JSON.stringify(data || previousData, null, 2)}</pre>
      <button
        onClick={() => {
          if (!data) {
            return;
          }
          setAfter(data.user.posts.pageInfo.endCursor);
        }}
      >
        Next Page
      </button>
    </div>
  );
};

@somnas, did you find a solution? I guess we need some way to tell the field policy to only ignore the pagination arguments (first, after, last, before) when we want to paginate infinitely?

I haven’t been able to find an decent solution, but for now we’re doing this:

const infinitePagination = Symbol('infinitePagination');

const optionalRelayStylePagination = (keyArgs: string[] = []) =>
  relayStylePagination((_, context) =>
    context.variables && context.variables[infinitePagination]
      ? keyArgs
      : ['first', 'after', 'last', 'before', ...keyArgs]
  );

const cache = new InMemoryCache({
  typePolicies: {
    User: {
      fields: {
        posts: optionalRelayStylePagination()
      }
    }
  }
});

const InfinitePagination: React.FunctionComponent<any> = () => {
  const { data, fetchMore } = useQuery(QUERY, {
    variables: {
      first: 2,
     [infinitePagination]: true // HACK: Tell type policy to ignore pagination params (`first`, `last`, `after`, `before`)
    }
  });
  return (
    <div style={{ color: 'limegreen' }}>
      <pre>{JSON.stringify(data, null, 2)}</pre>
      <button
        onClick={() => {
          fetchMore({
            variables: {
              after: data.user.posts.pageInfo.endCursor
            }
          });
        }}
      >
        Load more
      </button>
    </div>
  );
};

const RegularPagination: React.FunctionComponent<any> = () => {
  const [after, setAfter] = useState();
  const { data, previousData } = useQuery(QUERY, {
    variables: { first: 2, after }
  });
  return (
    <div style={{ color: 'lightblue' }}>
      <pre>{JSON.stringify(data || previousData, null, 2)}</pre>
      <button
        onClick={() => {
          if (!data) {
            return;
          }
          setAfter(data.user.posts.pageInfo.endCursor);
        }}
      >
        Next Page
      </button>
    </div>
  );
};

There’s some related discussion here Pagination cache rules in one place, not full app