Skip to content

Pagination & Caching with Server-Preloaded Client Components #552

Description

@kmgdevelopment

In Next.js I'm using a Server Component to preload data into a Client Component using preloadQuery and useSuspenseQuery. If javascript is enabled, the Client Component uses fetchMore + offsetLimitPagination() to do an infinite scroll. This all works fine.

When javascript is disabled, a classic link-based pagination is displayed which uses a page url query to display each page of results. The page query string value is passed to the component and used to set the offset variable before the query is executed.

Server component:

export default function RecipeEntriesList({ pageNum }: { pageNum: number }) {
  // query variables are initialized in the
  // Server Component then passed to the Client Component
  const queryLimit = 10;

  const queryVariables: QueryVariables = {
    section: ["recipes"],
    limit: queryLimit,
    offset: (pageNum - 1) * queryLimit, // 0 if on the first page
  };

  return (
    <PreloadQuery query={GET_RECIPE_ENTRIES} variables={{ queryVariables }}>
      <RecipeEntriesListClient queryVariables={queryVariables} />
    </PreloadQuery>
  );
}

Client component:

export default function RecipeEntriesListClient({
  queryVariables,
}: {
  queryVariables: QueryVariables;
}) {
  // offset is mutable in the client component
  // so we copy the initial value into a ref
  const queryOffset = useRef(queryVariables.offset);

  const { data, error, networkStatus, fetchMore } =
    useSuspenseQuery<RecipeListingQuery>(GET_RECIPE_ENTRIES, {
      variables: queryVariables, // defined in the Recipe Entries server component
    });

  const inViewRef = useOnInView((inView) => {
    if (inView && data && data.recipeList.length < data.entryCount) {
      fetchMore({
        variables: {
          offset: (queryOffset.current = data.recipeList.length),
        },
      });
    }
  });

  // ...

This does not work as expected. The entries from page 1 are displayed no matter which page query string is being accessed. I assume this has to do with how Apollo is caching the entries, but I don't know what I'm doing wrong. I have a separate InMemoryCache for the server component that lists offset as a keyArg but it doesn't seem to have an effect:

const keyArgs = ["section", "offset"];

const queryCache = new InMemoryCache({
    typePolicies: {
        Query: {
            fields: {
                entries: {
                    keyArgs: keyArgs
                },
                entryCount: {
                    keyArgs: keyArgs,
                },
            },
        },
    },
});

I'm surprised this is happening since the server cache gets thrown out immediately anyway so I don't really understand what's wrong. Here's a CodeSandbox with the full setup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions