Skip to main content

ProductGrid

ProductGrid lays out ProductCard items and handles loading, empty, error, and client-side pagination states.

Live Editor
function ProductGridExample() {
  const products = [
    {
      id: "serum",
      name: "Hydrating Face Serum",
      image: Stock,
      price: 36,
      originalPrice: 40,
      discount: 10,
      rating: 4.5,
      reviewCount: 128,
      badge: "New",
    },
    {
      id: "cleanser",
      name: "Daily Gel Cleanser",
      image: Stock,
      price: 24,
      rating: 4,
      reviewCount: 82,
    },
    {
      id: "cream",
      name: "Barrier Repair Cream",
      image: Stock,
      price: 42,
      rating: 5,
      reviewCount: 51,
      favorite: true,
    },
  ];

  return (
    <div className="w-full">
      <ProductGrid
        products={products}
        columns={3}
        paginated={false}
        onAddToCart={(product) => window.alert(`Added ${product.name}`)}
      />
    </div>
  );
}
Result
Loading...
import { ProductGrid } from "niom-parts";

<ProductGrid
products={products}
columns={4}
productsPerPage={8}
paginated
onProductClick={openProduct}
onFavorite={toggleFavorite}
onAddToCart={addToCart}
/>

Props​

PropTypeDefault
productsProductCardProps[]required
loadingbooleanfalse
skeletonCountnumber8
emptyMessageReactNode"No products found."
errorReactNode-
columns2 | 3 | 4 | 54
paginatedbooleantrue
productsPerPagenumber8
onProductClick(product) => void-
onFavorite(product) => void-
onAddToCart(product) => void-
classNamestring-

Common use cases​

Switch between the common asynchronous states below. The ready state also demonstrates client-side pagination.

Live Editor
function ProductGridStates() {
  const [state, setState] = React.useState("ready");
  const products = Array.from({ length: 5 }, (_, index) => ({
    id: `product-${index + 1}`,
    name: `Product ${index + 1}`,
    image: Stock,
    price: 20 + index * 4,
    rating: 4 + (index % 2) * 0.5,
    reviewCount: 12 + index,
  }));

  return (
    <div className="grid w-full gap-5">
      <div className="flex flex-wrap gap-2">
        {["ready", "loading", "empty", "error"].map((value) => (
          <Button
            key={value}
            size="sm"
            variant={state === value ? "primary" : "outline"}
            onClick={() => setState(value)}
          >
            {value}
          </Button>
        ))}
      </div>
      <ProductGrid
        products={state === "ready" ? products : []}
        loading={state === "loading"}
        skeletonCount={3}
        emptyMessage="No matching products."
        error={state === "error" ? "Products could not be loaded." : undefined}
        columns={3}
        productsPerPage={3}
        paginated
      />
    </div>
  );
}
Result
Loading...