Search code examples
reactjsgraphqlapollo

GraphQL fetchMore pagination with multiple queries


I have a page that contains several horizontal scrolling lists of items (let's call them blog posts, or whatever). I want each list to have pagination, but I don't want to make n HTTP requests when the page loads, and I also don't want to re-request the other lists when I'm loading the next page of results for one of them.

Here's what the query looks like:

const QUERY = gql`
  query Sections(
    $favoritesOffset: Int = 0
    $featuredOffset: Int = 0
    $recentOffset: Int = 0
  ) {
    favorites: events(limit: 5, offset: $favoritesOffset, search: "FAVORITES") {
      ...listResponse
    }
    featured: events(limit: 5, offset: $featuredOffset, search: "FEATURED") {
      ...listResponse
    }
    recent: events(limit: 10, offset: $recentOffset, search: "RECENT") {
      ...listResponse
    }
  }
`;

I'm currently using Apollo client & server.

I want to be able to make one request for all 3 lists when the page loads and then use fetchMore to only load the next page of one of them.


Solution

  • So far, my solution is to create one top-level query that contains all sections and then additional queries for the paginated responses of each section, then merged the data from the additional queries.

    Using components makes this a bit cleaner.

    Simplified example:

    Parent.tsx - With top-level query:

    const QUERY = gql`
      query Sections(
        $favoritesOffset: Int = 0
        $featuredOffset: Int = 0
        $recentOffset: Int = 0
      ) {
        favorites: events(limit: 5, offset: $favoritesOffset, search: "FAVORITES") {
          ...listResponse
        }
        featured: events(limit: 5, offset: $featuredOffset, search: "FEATURED") {
          ...listResponse
        }
        recent: events(limit: 10, offset: $recentOffset, search: "RECENT") {
          ...listResponse
        }
      }
      ${LIST_RESPONSE}
    `;
    
    const Page = () => {
      const { data, loading } = useQuery(QUERY);
    
      return (
        <>
          <Section initialData={results. favorites} search="FAVORITES" />
          <Section initialData={results. featured} search="FEATURED" />
          <Section initialData={results. recent} search="RECENT" />
        </>
      }
    }
    

    Section.tsx

    const FETCH_MORE = gql`
      query SectionsFetchMore($search: String, $offset: Int, $limit: Int = 10) {
        ...listResponse
      }
      ${LIST_RESPONSE}
    `;
    
    
    const Section = ({search, initialData}) => {
      const { data: fetchedData, fetchMore } = useQuery(FETCH_MORE, {
        skip: true, // IMPORTANT
        variables: { search, limit: 10, offset: 0 },
      });
    
    
      const fetchNextPage = useCallback(async (nextOffset) => {
        await fetchMore({ variables: { offset: nextOffset } });
    
        // Then merge data, either:
        //  + use client.cache.updateQuery() to update parent cache
        //  + keep local state that combines initialData and fetchedData
      }, [fetchMore]);
    
      return (
        // ... display list ...
      );
    };
    

    Merging

    In my case, I merged the cache locally to keep things simple:

      const data = useMemo(() => {
        if (!fetchedData?.sections) {
          return initialData;
        }
        return {
          nodes: [...initialData.nodes, ...fetchedData?.sections?.nodes],
          pageInfo: fetchedData.sections.pageInfo,
        };
      }, [fetchedData, initialData]);
    

    But, you could also update the apollo cache using client.cache.updateQuery() (more info).