Testimonials
The store reviews page, whose loader only sets a title while Salla's comments web component loads and sorts the reviews in the browser.
import { Testimonials, TestimonialsPage, testimonialsLoader, TestimonialsPageProps } from '@salla.sa/twilight-theme-engine/routes/testimonials';In plain words
Shoppers review the store itself, not only its products. /testimonials lists those reviews with a sort menu (latest, oldest, top rated, low rated). The list is Salla's ready-made comments web component, a custom HTML element that fetches and draws its own content, so the server sends only the page title. See it on Built-in pages.
Signature
const Testimonials: {
readonly id: 'testimonials';
readonly loader: (
ctx: { locale?: string },
extend?: (data: TestimonialsPageProps, ctx: { params: {} }) => Record<string, unknown> | Promise<Record<string, unknown>>
) => Promise<TestimonialsPageProps>;
readonly head: (ctx: TwilightContext, data: TestimonialsPageProps) => HeadDescriptor;
readonly Component: React.LazyExoticComponent<(props: TestimonialsPageProps) => JSX.Element>;
};
const TestimonialsPage: typeof Testimonials.Component;
function testimonialsLoader(ctx: { locale: string }): Promise<TestimonialsPageProps>;
interface TestimonialsPageProps {
page: Page; // title: the blocks.home.testimonials translation, or 'Customer Reviews'
}Example
import { createFileRoute } from '@tanstack/react-router';
import { Testimonials } from '@salla.sa/twilight-theme-engine/routes/testimonials';
import type { TestimonialsPageProps } from '@salla.sa/twilight-theme-engine/routes/testimonials';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';
export const Route = createFileRoute('/{-$locale}/testimonials')({
loader: ({ params }): Promise<TestimonialsPageProps> =>
Testimonials.loader({ locale: params.locale }),
head: withHead(Testimonials),
component: TestimonialsComponent,
});
function TestimonialsComponent() {
const data: TestimonialsPageProps = Route.useLoaderData();
return <Testimonials.Component {...data} />;
}
How it behaves
testimonialsLoadermakes no request and ignoreslocale.Testimonials.loaderrequires its context object ({}is enough) and passeslocale ?? 'ar'on.headsets the title and hreflang alternates only.The sort menu starts from
?sort=in the address (defaultlatest). An effect writes the current value back withhistory.replaceState, on mount and on every change, so opening the page adds?sort=latestto the address.useCommentsremounts the comments web component when a review is added. The page has no hook slots.Lazy: outside a route, wrap
TestimonialsPagein<Suspense>.
Gotchas
TestimonialsPagereadswindow.location.searchwhile it renders (in auseStateinitializer, packages/theme-engine/src/routes/testimonials/TestimonialsPage.tsx). The server has nowindow, so the server render of the page throwswindow is not defined. In the generated route the router's Suspense boundary catches that: React sends the pending skeleton and renders the page in the browser, logging the error. Anywhere else, render the component only in the browser, for example behinduseIsClient().It writes
?sort=withhistory.replaceStatedirectly, outside the router, on mount and on every change. The router's ownlocation.searchnever contains thatsort, and a component that reusesTestimonialsPagerewrites the address of whatever page it is on.
Related
A React key that goes up whenever a shopper posts a comment, so Salla's comments component reloads without a page refresh.
useIsClientReturns false while the page is rendered on the server and during hydration, then true once the component has mounted in the browser.
TestimonialsShows the store's customer reviews, fetched by Salla's reviews web component, with type, sort, limit and privacy options.
Source and docs
- Engine source:
packages/theme-engine/src/routes/testimonials/index.tsx