# Defaults: Reviews (/themes/sdk/reference/product/defaults-8)

```ts
import { … } from '@poltura/theme-sdk/product';
```

## `Reviews` [#reviews]

The complete reviews panel: heading, score, the reviews with load more, and the write-a-review form.

```ts
Reviews(props: ReviewsProps): ReactElement | null
```

**Props:** [`ReviewsProps`](/themes/sdk/reference/product/types-8#reviewsprops)

<Tab value='Example'>

```tsx
import { Reviews } from '@poltura/theme-sdk/product';

export function ProductReviews() {
  return <Reviews heading='What buyers say' />;
}
```

</Tab>
<Tab value='Source'>

```tsx title="source/reviews.tsx"
import { useMemo, useState, type ReactElement } from 'react';
import { useProducts } from '@poltura/theme-sdk/data';
import { usePreviewScenario } from '@poltura/theme-sdk/editor';
import { useTranslations } from '@poltura/theme-sdk/i18n';
import {
  RatingSummary,
  ReviewCard,
  ReviewForm,
  ReviewSkeleton,
  ReviewsEmpty,
  ReviewsFooter,
  ReviewsHeader,
  useProductPage,
  useReviews,
  type RatingStats,
  type Review,
  type ReviewTarget,
  type ReviewsBy,
  type ReviewsProps,
} from '@poltura/theme-sdk/product';

const PAGE_SIZE = 3;

const SAMPLE_STATS: RatingStats = {
  average: 4.5,
  count: 2,
  breakdown: [
    { stars: 5, count: 1 },
    { stars: 4, count: 1 },
    { stars: 3, count: 0 },
    { stars: 2, count: 0 },
    { stars: 1, count: 0 },
  ],
};

function useSampleReviews(): readonly Review[] {
  const t = useTranslations('reviews.sample');
  return useMemo(
    () => [
      {
        id: 'review_preview_1',
        productId: 'preview',
        author: t('firstAuthor'),
        rating: 5,
        title: t('firstTitle'),
        body: t('firstBody'),
        verifiedPurchase: true,
        createdAt: '2026-06-14T10:00:00.000Z',
      },
      {
        id: 'review_preview_2',
        productId: 'preview',
        author: t('secondAuthor'),
        rating: 4,
        title: t('secondTitle'),
        body: t('secondBody'),
        verifiedPurchase: false,
        createdAt: '2026-07-02T18:30:00.000Z',
      },
    ],
    [t],
  );
}

type PanelProps = {
  target: ReviewTarget;
  heading: string | undefined;
  className: string;
  listClassName: string;
};

function reviewsOf(target: ReviewTarget): ReviewsBy {
  return 'products' in target
    ? { productIds: target.products.map((product) => product.id) }
    : { productId: target.product.id };
}

function ReviewsPanel({ target, heading, className, listClassName }: PanelProps): ReactElement {
  const [limit, setLimit] = useState(PAGE_SIZE);
  const [newFrom, setNewFrom] = useState<number | null>(null);
  const [writing, setWriting] = useState(false);
  const scenario = usePreviewScenario('reviews');
  const samples = useSampleReviews();
  const by = useMemo(() => reviewsOf(target), [target]);
  const { data, isFetching } = useReviews(by, { limit }, { keepPrevious: true });
  const loading = data === undefined && isFetching;
  const live = data?.reviews ?? [];
  const sampled = scenario === 'sample' && !loading && live.length === 0;
  const hidden = scenario === 'empty';
  const reviews = hidden ? [] : sampled ? samples : live;
  const stats = hidden ? null : sampled ? SAMPLE_STATS : (data?.stats ?? null);
  const shownLoading = !hidden && loading;
  const titles = useMemo(
    () =>
      'products' in target
        ? new Map(target.products.map((product) => [product.id, product.title]))
        : null,
    [target],
  );
  const toggle = () => setWriting((current) => !current);

  return (
    <section className={className}>
      <ReviewsHeader
        heading={heading}
        total={stats?.count ?? 0}
        onWrite={reviews.length > 0 ? toggle : undefined}
      />
      {writing ? <ReviewForm target={target} onClose={() => setWriting(false)} /> : null}
      {stats !== null && stats.count > 0 ? (
        <RatingSummary stats={stats} policy={sampled ? undefined : data?.policy} />
      ) : null}
      {shownLoading ? <ReviewSkeleton /> : null}
      {!shownLoading && reviews.length === 0 ? (
        <ReviewsEmpty onWrite={writing ? undefined : toggle} />
      ) : null}
      {!shownLoading && reviews.length > 0 ? (
        <>
          <div className={listClassName}>
            {reviews.map((review, index) => (
              <ReviewCard
                key={review.id}
                review={review}
                productLabel={titles?.get(review.productId)}
                isNew={newFrom !== null && index >= newFrom}
              />
            ))}
          </div>
          <ReviewsFooter
            loaded={reviews.length}
            hasMore={!sampled && (data?.pageCount ?? 1) > 1}
            loadingMore={isFetching && data !== undefined}
            onLoadMore={() => {
              setNewFrom(reviews.length);
              setLimit((current) => current + PAGE_SIZE);
            }}
          />
        </>
      ) : null}
    </section>
  );
}

function PageReviews(props: Omit<PanelProps, 'target'>): ReactElement {
  const { reviewTarget } = useProductPage();
  return <ReviewsPanel target={reviewTarget} {...props} />;
}

function ChosenReviews({
  by,
  ...props
}: Omit<PanelProps, 'target'> & { by: ReviewsBy }): ReactElement | null {
  const ids = useMemo(() => ('productId' in by ? [by.productId] : [...by.productIds]), [by]);
  const { data } = useProducts(
    { ids, limit: Math.max(1, ids.length) },
    { enabled: ids.length > 0 },
  );
  const target = useMemo<ReviewTarget | null>(() => {
    const products = ids.flatMap((id) => data?.items.filter((product) => product.id === id) ?? []);
    const first = products[0];
    if (first === undefined) return null;
    if ('productId' in by) return { product: { id: first.id, handle: first.handle } };
    return {
      products: products.map((product) => ({
        id: product.id,
        handle: product.handle,
        title: product.title,
      })),
    };
  }, [by, data, ids]);
  return target === null ? null : <ReviewsPanel target={target} {...props} />;
}

export function Reviews({
  by,
  heading,
  className = 'border-t border-border pt-10 space-y-8',
  listClassName = 'space-y-3',
}: ReviewsProps): ReactElement | null {
  const props = { heading, className, listClassName };
  return by === undefined ? <PageReviews {...props} /> : <ChosenReviews by={by} {...props} />;
}
```

</Tab>


**See also:** [`useReviews`](/themes/sdk/reference/product/hooks-2#usereviews), [`useSubmitReview`](/themes/sdk/reference/product/hooks-2#usesubmitreview), [`ReviewCard`](/themes/sdk/reference/product/parts-3#reviewcard), [`ReviewForm`](/themes/sdk/reference/product/parts-4#reviewform)
