React widgets
@cascade-commerce/widgets is the React package behind the
storefront widgets. If your storefront is a React app, you can import the
widgets directly instead of dropping in the embed script, and you can take any widget apart
and lay its pieces out yourself.
This page covers the three depths you can work at, the parts each widget exposes, and the props each part takes. It is written to be precise enough to build from: if you are handing it to a code assistant, hand it the whole page.
Setup
Wrap the part of your tree that renders widgets in a CascadeProvider. Every connected
widget reads the API base URL, the publishable key and the signed-in customer from it.
customerAuth is the same signed trio the embed script takes. See
Identifying the customer for how to make it.
Leave it null for a signed-out shopper.
Each widget's stylesheet is imported by the widget itself. The custom properties and class
names in Appearance apply exactly as they do to the embed, so a
theme set in the dashboard reaches a React storefront too, provided you link the served
stylesheet from your page's <head>.
Two components per widget
Every widget ships as a pair:
The connected widget talks to the API: it fetches, paginates, votes, submits, and handles the shopper's sign-in state. The presentational component takes everything as props and fetches nothing. You will mostly use the connected one; the presentational one is for when you already have the data, or for a preview against sample data.
Level one: drop it in
Every connected widget renders its default composition with no children. Its props have not changed; see Connected widget props.
Level two: lay the parts out yourself
Give the connected widget children and it renders them instead of the default composition, inside a root it has filled with what it fetched. Each part reads that data and draws its own internals. How the parts sit relative to each other, and any markup between them, is up to you.
Four rules hold for every widget:
- Parts are properties of the presentational component,
ProductReviews.List, never separate imports. Import the presentational component to reach them. - A part takes no data props. It reads the root. Leaving a part out leaves that piece off the page; a part with nothing to show (no summary, no more pages, no form handler) renders nothing.
- The root element and its class names are unchanged. The connected widget still
renders
.cascade.cascade-reviewsaround your children, so custom CSS written against the documented classes keeps applying, and anything with structured data still emits it. - With the presentational component, the root is explicit. Pass the same props you
would pass to the component to
<ProductReviews.Root>and put the parts inside it. The connected widget does exactly this for you, which is why you do not writeRootinside it.
Level three: recompose a row
A list part renders its children once per item, inside a context holding that item. With no children it renders the default row. So a review row can be rebuilt from its own parts, or replaced with a component of yours:
Every widget with a list exposes the same shape: a list part that takes children, a default row, and a hook that returns the current item. The hooks are listed per widget below.
Writing a part of your own
Every presentational component exposes useData(), which returns what its Root was
given with defaults applied, plus any state the root keeps for its parts (which tile is
open, whether the popover is showing). A custom part is an ordinary component that calls
it:
A hook used outside its root throws with the name of the root it needs, for example
This part has to be rendered inside <ProductReviews.Root>.
The parts, widget by widget
Each table lists a part, what it renders, and when it renders nothing. A part's props are
listed where it takes any; most take none. children on a list part are rendered once
per item; empty replaces the default empty state.
ProductReviews
Root element: div.cascade.cascade-reviews. Default order: Summary, QuestionSummary,
WriteReview, AiSummary, AskBox, Toolbar, List, LoadMore, Questions.
ProductReviews.Review parts, valid inside List:
ProductRating
Root element: div.cascade.cascade-rating. Default: Stars, then Count unless
hideCount.
ReviewsCarousel
Root element: section.cascade.cascade-reviews-carousel. Default: Header, Track.
With no reviews the root renders the empty state (or its empty prop) whatever children
it was given.
ReviewsCarousel.Card parts, valid inside Track: Root (li.cascade-reviews-carousel-card,
takes children), Rating, Title, Body, Image (the first photo), Footer (name
and verified badge), Product (nothing unless showProduct and the review names one),
and useCard() for the current review.
MediaGrid
Root element: section.cascade.cascade-media-grid. Default: Heading, Tiles,
LoadMore, Lightbox. With no items the root renders the empty state (or its empty
prop) whatever children it was given.
A custom tile should call open() from useTile() to show the lightbox, and the
composition should include <MediaGrid.Lightbox /> somewhere for it to open into.
CustomerWishlists
Root element: div.cascade.cascade-wishlists. Default: ErrorNotice, Overview,
Detail, ShareDialog. The Overview parts render only while no list is open and the
Detail parts only while one is, so a composition can include both.
AddToWishlist
Root element: div.cascade.cascade-add-to-wishlist. Default: Controls, Popover,
Toast, ErrorNotice. The root closes the popover on a click anywhere outside it.
WishlistsModal
Root element: dialog.cascade.cascade-wishlists-modal. This root is the exception:
its children are the dialog's content, not a default composition, because a modal has
nothing to draw until it is told what goes in it. <WishlistsModal> itself renders
Header then Body around its children.
WishlistsModalWidget has two parts of its own, since it owns the open state:
WishlistsModalWidget.Trigger (the button) and WishlistsModalWidget.Dialog (the
modal, holding <CustomerWishlistsWidget /> unless given children). Pass children to
the widget to place them apart.
LoyaltyPanel
Root element: div.cascade.cascade-loyalty. Default: Header, Notices,
ProgramTabs, ErrorNotice, HeldRewards, Sections, Birthday, Referral. Without
a program the root renders the "no program yet" line (or the error) whatever children it
was given.
A custom row inside any of the list parts should render its own <li>; the list part
renders the <ul>.
LoyaltyLauncher
Root element: div.cascade.cascade-loyalty-launcher. Default: Panel, Button.
<LoyaltyLauncher> itself puts its children inside Panel.
LoyaltyPoints
Root element: a.cascade.cascade-loyalty-points when href is set, otherwise
p.cascade.cascade-loyalty-points. Default: Label. The root renders nothing at all
when points is zero or less, whatever children it was given.
Connected widget props
The connected widgets take the props below, plus children on every one of them. A
connected widget renders nothing until its first response has arrived, so a part never
sees a half-loaded root.
What children do not change: ProductReviewsWidget and ProductRatingWidget still emit
their structured data, AddToWishlistWidget still renders the manage dialog beside the
button, and CustomerWishlistsWidget still switches between the signed-in and guest
lists.
Presentational component props
The presentational components' props are the data the connected widget would fetch plus the handlers it would wire up. They are typed in the package; the shapes worth knowing:
ProductReviews:averageRating,totalReviews,ratingBreakdown,reviews,hasMore, and optionallysummary,reviewAnswers,questions,sort,activeRating,loading,signedInEmail, the star props, and the handlersonLoadMore,onSortChange,onRatingChange,onVote,onSubmitReview,onUploadReviewFile,onAsk,onLoadMoreQuestions,onAnswerQuestion.ReviewsCarousel:reviews,heading,showProduct, the star props.MediaGrid:items,heading,showProduct,hasMore,onLoadMore,loading, the star props.CustomerWishlists:wishlists,activeWishlist,error,loading,sharingEnabled,multipleWishlists,guest, and one handler per action.AddToWishlist:wishlists,loading,pending,error,disabled,multipleWishlists,onToggleDefault,onAddToWishlist,onRemoveFromWishlist,onManageWishlists.LoyaltyPanel:program,programs,loading,error,canJoin,currencyCode,onJoin,onClaim,onCancelReward,onSelectProgram,onSaveBirthday.LoyaltyPoints:points,pointsNames,member,href,className.
Every type is exported from the package root: Review, ReviewSummary,
CarouselReview, MediaGridItem, WishlistSummary, WishlistDetail, WishlistItem,
AddToWishlistOption, LoyaltyProgram, LoyaltyMember, LoyaltyRule, LoyaltyReward
and the rest.
A worked example
packages/example-custom-store in the Cascade repository is a React storefront that uses
the connected widgets on every page it belongs on. Its product page lays the reviews
widget out from parts: the rating summary, the breakdown and the write button in one row,
then the rest in the default order. See Custom integration
for the store as a whole.