Category page

The framework comes with two variants of category pages, one with so called "infinite pagination" and one with regular page based pagination. Usually you select either one, where the infinite pagination is the standard one. For improved performance you can safely remove the one you're not using from the code.

Header

The category header is rendered either based on a CategoryHeader element added in the content editor, or based on the fields from the Category edit view in Jetshop admin.

Infinite pagination

The infinite pagination variant has a "Show more" button below the product grid, and pressing that button will load more products and append them to the end of the grid. To make sure that we don't suffer from bad performance when a big amount of products is rendered, we use a technique called windowing, which lets us only render the products that are in view right now.

A really important thing to know when working with this windowed product grid is that all product cards need to have the same exact height. If you experience that the grid is jumping when scrolling down, that is usually a sign that you have product cards of varying height.

Page based pagination

The regular page based pagination will display previous and next buttons at the bottom of the product grid, which lets you step between pages.

Filtering and sorting

There's support for filtering and sorting based on the setup in Jetshop. You can read more about the different kinds of filtering available under the Filters section in the menu.

Pagination

Regular Pagination

The framework comes with infinite pagination by default. If you would like to use regular pagination you can find an example of it for the category page in the StandargCategoryPage.js file in your repository and for the search page the SearchResults.js file. Also be sure to remove 'paginationChange' from the ScrollRestorationHandler's ignoreForRouteTypes prop so that the page will scroll to the top on pagination change.

Copy
Copied
<ScrollRestorationHandler
  ignoreForRouteTypes={["sortOrderChange", "filterChange"]}
/>

Infinite pagination

It supports so called "infinite pagination" with windowing through some helper hooks and components. This means that you can render really big product grids on one page, while still maintaining good performance.

This is commonly used together with a "Show more" button, or that the following products are loaded when you scroll, using an IntersectionObserver.

useInfinitePagination

In order to show products from multiple pages, as well as provide functionality for loading the following pages, you can use the hook useInfinitePagination.

This hook takes the result from your routeQuery, as well as the query itself, and makes sure that the proper queries are run and appended to the result whenever more products are requested.

import useInfinitePagination from '@jetshop/core/hooks/useInfinitePagination';

Arguments

result: QueryResult Pass this directly from the RouteResolver.

query: DocumentNode Pass the exact same query as the one you pass to the RouteResolver.

Example:

Copy
Copied
import routeQuery from "./RouteQuery.gql";

const CategoryPage = ({ result }) => {
  const { products, previous, next } = useInfinitePagination({
    result,
    query: routeQuery,
  });
};

Return value

products: Product[]

A list of all the products that should be rendered.

previous and next

Objects containing the following data for previous/next page of products:

  • loadingProducts: boolean A boolean to tell if we're loading products for this page.
  • fetchProducts: () => void Call this to start fetching the products.
  • offset: number The offset that is passed to the query that is run when you run fetchProducts.
  • page: number The page number for the previous/next page. Use this to generate a fallback pagination link.

WindowGrid

See documentation for WindowGrid.

Scroll restoration

In order for the scroll restoration of the windowing to work properly, you need to add the exception paginationChange to the ScrollRestorationHandler component.

In your Shop.js, where the ScrollRestorationHandler is rendered, add paginationChange to the ignoreForRouteTypes prop:

Copy
Copied
<ScrollRestorationHandler
  ignoreForRouteTypes={["sortOrderChange", "filterChange", "paginationChange"]}
/>

Usage

We highly recommend using useInfinitePagination and WindowGrid together, they are built for this.

Note: There are some caveats when implementing WindowGrid, so please read the documentation thoroughly.

This is an example of a simple category page that uses infinite pagination:

Copy
Copied
import React from "react";
import { WindowGrid } from "@jetshop/ui/WindowGrid";
import useInfinitePagination from "@jetshop/core/hooks/useInfinitePagination";
import qs from "qs";
import { useLocation } from "react-router";
import { Link } from "react-router-dom";
import ProductCard from "./ProductCard";
import routeQuery from "./RouteQuery.gql";

function ProductRenderer({ style, innerRef, item }) {
  return (
    <div style={style} ref={innerRef}>
      <ProductCard product={item} />
    </div>
  );
}

const CategoryPage = ({ result }) => {
  const location = useLocation();
  const { products, previous, next } = useInfinitePagination({
    result,
    query: routeQuery,
  });

  const params = qs.parse(location.search, { ignoreQueryPrefix: true });

  return (
    <>
      {previous.hasProducts && (
        <Link
          // The to attribute is used to generate a real <a> tag when server side rendering which means pagination will work without javascript enabled
          to={{
            ...location,
            search: qs.stringify({
              ...params,
              page: previous.page,
            }),
          }}
          // On the client however, we want to use the fetchProducts function that useInfinitePagination exposes
          onClick={(e) => {
            e.preventDefault();
            previous.fetchProducts();
          }}
        >
          Show more
        </Link>
      )}
      <WindowGrid
        id="category"
        products={products}
        prevOffset={previous.offset}
        itemsPerRow={[2, 3, null, 4]}
        component={ProductRenderer}
      />
      {next.hasProducts && (
        <Link
          to={{
            ...location,
            search: qs.stringify({
              ...params,
              page: next.page,
            }),
          }}
          onClick={(e) => {
            e.preventDefault();
            next.fetchProducts();
          }}
        >
          Show more
        </Link>
      )}
    </>
  );
};
Copyright © Norce 2023. All right reserved.