You can implement SmartWishlist functionality to help customers save products for future consideration, which tends to improve customer engagement and encourages return visits to your store. Wishlists enable shoppers to curate personalized product collections, making it easier for them to track items they're interested in and complete purchases when they're ready.
Integrating wishlist functionality into your Merchandising Cloud product cards helps streamline the shopping experience by allowing customers to easily manage their saved items directly from search results and collection pages. This feature generally enhances customer retention and can help improve conversion rates by reducing the friction in the purchase journey.

SmartWishlist
Integration Step-By-Step
First of all you need to create a getWishList function with one argument - setList.This argument is used for updating state in the layouts/Search component. Then you need to define an array of the wishlist items (wishListArray) and pass it to setList.
getWishList,tsx
const getWishList = ( setList ) =>{ // there should be a code for getting an array of wishListArray setList(wishListArray);}export default getWishList;
After that you need to import getWishList to layouts/Search component and call the function on initial render. Inside of the layouts/Search component you also need to create a state for your wishList and addToWishListArray function.
layouts/Search
/** code */const [wishList, setList] = useState([])// updating wishList on initial renderuseEffect(() => { const timeout = setTimeout(() => { getWishList(setList) }, 500) return () => { clearTimeout(timeout); }}, []);// function for updating wishList on clickconst addToWishListArray = (id, selectedId) => { const isInWishList = wishList && wishList.find(i => { return i.indexOf(id) > -1 }) setList(wishList => { return isInWishList ? wishList.filter(i => i.indexOf(id) == -1) : [...wishList, [id, selectedId, 1]] })}
The final result of the above implementation may look like this:
layouts/Search
import { useEffect } from 'react';import StaticResults from 'components/search/StaticResults';import LazyResults from 'components/search/LazyResults';import DesktopFacets from 'components/search/DesktopFacets';import MobileActions from 'components/search/MobileActions';import DesktopActions from 'components/search/DesktopActions';import Branch from 'components/common/Branch';import Banner from 'components/Banner';import { List } from 'immutable';import Grid, { Column } from 'components/common/Grid';import { useMobile } from 'helpers/useMobile';import { useAnnouncement } from 'components/common/Announcement';import useScrollOnChange from 'helpers/useScrollOnChange';import { useItems } from '@findify /react-connect';import useTranslations from 'helpers/useTranslations';import styles from 'layouts/Search/styles.css';import { Immutable } from '@findify /store-configuration';import { ThemedSFCProps, IProduct } from 'types';import getWishList from "getWishList";/** Props that search layout accepts */export interface ISearchProps extends ThemedSFCProps<typeof styles> { isCollection?: boolean; /** Items list */ items: List<IProduct>;}const Search = ({ isCollection, theme = styles }) => { const { items, config } = useItems<Immutable.SearchConfig>(); const translate = useTranslations(); const isMobile = useMobile(); const [announcement, setAnnouncement] = useAnnouncement(); //create state for wishList const [wishList, setList] = useState([]) useScrollOnChange(items); useEffect(() => setAnnouncement(translate('search.accessibleUpdate')), [ items, ]); useEffect(() => { const timeout = setTimeout(() => { //getting wishList getWishList(setList) }, 500) return () => { clearTimeout(timeout); }} , []); const addToWishListArray = (id) => { const isInWishList = wishList && wishList.indexOf(id) > -1; setList(wishList => { return isInWishList ? wishList.filter(i => i !== id) : [...wishList, id] }) } if (!items.size) return null; return ( <> <Grid className={theme.root} gutter={40} columns={ config.getIn(['facets', 'position']) === 'top' ? 'full' : config.getIn(['breakpoints', 'layout'], 'fit|auto') } > <Column display-if={!isMobile} order={config.getIn(['facets', 'position']) === 'right' && 2} > <DesktopFacets /> </Column> <> <Branch isCollection={isCollection} condition={isMobile} left={MobileActions} right={DesktopActions} /> <Banner /> <Branch condition={config.getIn(['pagination', 'type']) === 'lazy'} left={LazyResults} right={StaticResults} wishList={wishList} addToWishListArray={addToWishListArray} /> </> </Grid> {announcement} </> );};export default process.env.HOT ? require('react-hot-loader').hot(module)(Search) : Search;
After that, you need to create a new wishlist component, i.e. WishList.tsx. You need to check if the current product is already in the wishlist, and utilize addToSmartWishList/removeFromSmartWishList function to update the states accordingly.
WishList.tsx
import React from "react";import cx from "classnames";const WishListTootlip = ({ className, isInList }) => { const text = isInList ? "Remove from" : "Add to"; return( <div className={className} onClick={e => e.stopPropagation()}> <div className="findify-wishlist-tootlip__content"> {text} <a href="/a/wishlist">Wishlist</a> </div> <div className="findify-wishlist-tootlip__arrow"> <span className="findify-wishlist-tootlip__arrow-border"></span> <span style={{borderColor: "rgb(167, 156, 157)"}}></span> </div> </div> )}const WishList = ({ sustainable, wishList, item, addToWishListArray }) => { const id = item.get('id'); const selectedId = item.get('selected_variant_id'); const isInWishList = wishList && wishList.find(i => { return i.indexOf(id) > -1 }) const onClick = (e) => { e.stopPropagation(); if(isInWishList){ RemoveFromSmartWishlist(item.get('id'), item.get('selected_variant_id')); } else { AddToSmartWishlist(item.get('id'), item.get('selected_variant_id')); } addToWishListArray(id, selectedId) } return ( <span className="findify-wishlist" data-wishlist-btn="" data-product={item.get('id')} aria-label="Add to Wishlist" role="button" tabindex="0" > <div onClick={onClick} className="findify-wishlist__wrapper"> // use your Icons here </div> </span> )}export default WishList;
Finally, you can use custom WishList.tsx in the components/Cards/Product component:
components/Cards/Product
import cx from 'classnames';import Image from 'components/common/Image';import Rating from 'components/Cards/Product/Rating';import Price from 'components/Cards/Product/Price';import Title from 'components/Cards/Product/Title';import Description from 'components/Cards/Product/Description';import Variants from 'components/Cards/Product/Variants';import styles from 'components/Cards/Product/styles.css';import { DiscountSticker, OutOfStockSticker,} from 'components/Cards/Product/Stickers';import { List } from 'immutable';import { IProduct, ThemedSFCProps } from 'types';import { Immutable, Product } from '@findify /store-configuration';import trackProductPosition from 'helpers/trackProductPosition';import { useMemo, useState } from 'react';export interface IProductCardProps extends ThemedSFCProps { item: IProduct; config: Immutable.Factory<Product>; Container?: React.ElementType; highlighted: boolean; isAutocomplete?: boolean;}const useVariants = ( item): [IProduct, React.Dispatch<React.SetStateAction<string>>] => { const [currentVariant, setVariant] = useState<string>( item.get('selected_variant_id') ); const variant = useMemo( () => item.merge( item.get('variants')?.find((i) => i.get('id') === currentVariant) ), [currentVariant] ); return [variant, setVariant];};export default ({ item, theme = styles, className, config, Container = 'div', highlighted, isAutocomplete, wishList, addToWishListArray}: IProductCardProps) => { const container = trackProductPosition(item); const [variant, setVariant] = useVariants(item); return ( <Container ref={container} data-element="card" className={cx( theme.root, theme[config.get('template')], highlighted && theme.highlighted, isAutocomplete && theme.autocomplete, className )} > <div className={theme.content}> <Rating className={theme.rating} value={variant.getIn(['reviews', 'average_score'])} count={ variant.getIn(['reviews', 'count']) || variant.getIn(['reviews', 'total_reviews']) } display-if={ !!variant.getIn(['reviews', 'count']) || !!variant.getIn(['reviews', 'total_reviews']) } /> <Variants config={config} item={item} /> {/* Link hack: Title's "a" contains :after element with absolute position what makes provide link effect to the rest of card - To remove element from the effect set `position:relative` - Or `z-index: 1`, but it may have side effects */} <Title display-if={!!variant.get('title')} theme={theme} onClick={variant.onClick} href={variant.get('product_url')} text={variant.get('title')} /> <Description display-if={!!variant.get('description')} theme={theme} text={variant.get('description')} /> <div className={theme.divider} /> <Price display-if={!!variant.get('price')} className={theme.priceWrapper} item={item} /> <OutOfStockSticker display-if={variant.getIn(['stickers', 'out-of-stock'])} config={config} /> </div> {/* ADA specific hack: We need to make image belong to content, so we move it under the title. - flex order set to -1 */} <div className={theme.image} onClick={item.onClick}> <Image aspectRatio={config.getIn(['image', 'aspectRatio'])} thumbnail={variant.get('thumbnail_url')} alt={variant.get('title')} lazy={config.getIn(['image', 'lazy'])} offset={config.getIn(['image', 'lazyOffset'])} src={ config.getIn(['image', 'multiple']) ? [variant.get('image_url'), variant.get('image_2_url')] : variant.get('image_url') || variant.get('thumbnail_url') } /> <WishList wishList={wishList} item={item} display-if={!isAutocomplete} addToWishListArray={addToWishListArray} /> <DiscountSticker config={config} className={theme.discountSticker} discount={variant.get('discount')} display-if={ config.getIn(['stickers', 'discount']) && variant.get('discount', List()).size && variant.getIn(['stickers', 'discount']) } /> </div> </Container> );};
Related Articles
Integration & Setup:
Search & Discovery:
Product Recommendations: