PWA

How to get related, upsell and cross-sell products on product page in Magento 2 PWA

In Magento, we can get related, upsell, and cross-sell products on the product detail page using admin settings. But in PWA Studio, that feature is not available right now. Magento provides GraphQL to get linked products for the current product. So in PWA, we can create a custom component to fetch related, upsell, and cross-sell products of current products.

In this blog, we get an idea of how to fetch linked products data of the current product using GraphQL and display it on the product detail page. For that we have created a small example to display related, upsell and cross-sell products on the product detail page as follows:

Create a GraphQL file

File Path: src/components/LinkedProducts/linkedProducts.gql.js

import { gql } from '@apollo/client';

export const GET_LINKED_PRODUCTS = gql`
    query getRelatedProducts($sku: String!) {
        products(filter: { sku: { eq: $sku } }) {
            items {
                id
                name
                related_products {
                    id
                    name
                    small_image {
                        label
                        url
                    }
                    url_key
                    url_suffix
                    price_range {
                        minimum_price {
                            regular_price {
                                currency
                                value
                            }
                        }
                    }
                }
                upsell_products {
                    id
                    name
                    small_image {
                        label
                        url
                    }
                    url_key
                    url_suffix
                    price_range {
                        minimum_price {
                            regular_price {
                                currency
                                value
                            }
                        }
                    }
                }
                crosssell_products {
                    id
                    name
                    small_image {
                        label
                        url
                    }
                    url_key
                    url_suffix
                    price_range {
                        minimum_price {
                            regular_price {
                                currency
                                value
                            }
                        }
                    }
                }
            }
        }
    }
`;
Create a component file

File Path: src/components/LinkedProducts/linkedProducts.js

import React from "react";
import { useQuery } from '@apollo/client';
import { FormattedMessage } from 'react-intl';
import { GET_LINKED_PRODUCTS } from "./linkedProducts.gql";
import { fullPageLoadingIndicator } from "@magento/venia-ui/lib/components/LoadingIndicator";
import { Link, resourceUrl } from '@magento/venia-drivers';
import ErrorView from "@magento/venia-ui/lib/components/ErrorView";
import Image from "@magento/venia-ui/lib/components/Image";
import Price from "@magento/venia-ui/lib/components/Price";
import {UNCONSTRAINED_SIZE_KEY} from "@magento/peregrine/lib/talons/Image/useImage";

const LinkedProducts = props => {
    const { productSku } = productDetails.sku;

    const { loading, error, data } = useQuery(GET_LINKED_PRODUCTS, {
        variables: { sku: productSku },
        fetchPolicy: 'cache-and-network',
        nextFetchPolicy: 'cache-first'
    });

    if (!data) {
        if (loading) {
            return fullPageLoadingIndicator;
        }

        if (error) {
            return <ErrorView message={error.message} />;
        }
    }

    const product = data.products.items.length > 0 ? data.products.items[0] : null;

    const relatedProductsData = product ? product.related_products : null;
    const relatedProducts = relatedProductsData.length > 0 ? relatedProductsData : null;

    const upsellProductsData = product ? product.upsell_products : null;
    const upsellProducts = upsellProductsData.length > 0 ? upsellProductsData : null;

    const crosssellProductsData = product ? product.crosssell_products : null;
    const crosssellProducts = crosssellProductsData.length > 0 ? crosssellProductsData : null;

    const relatedItems = relatedProducts ? relatedProducts.map((item) => {

        const productLink = resourceUrl(`/${item.url_key}${item.url_suffix || ''}`);

        return (
            <div className={classes.root}>
                <Link
                    to={productLink}
                    className={classes.images}
                >
                    <Image
                        alt={item.small_image.label}
                        classes={{
                            image: classes.image,
                            root: classes.imageContainer
                        }}
                        height={IMAGE_HEIGHT}
                        resource={item.small_image.url}
                        widths={IMAGE_WIDTHS}
                    />
                </Link>
                <Link
                    to={productLink}
                    className={classes.name}
                >
                    <span>{item.name}</span>
                </Link>
                <div className={classes.price}>
                    <Price
                        value={item.price_range.minimum_price.regular_price.value}
                        currencyCode={item.price_range.minimum_price.regular_price.currency}
                    />
                </div>
            </div>
        );
    }) : null;

    const upsellItems = upsellProducts ? upsellProducts.map(... Do the same thing as relatedItems ...) : null;
    const crosssellItems = crosssellProducts ? crosssellProducts.map(... Do the same thing as relatedItems ...) : null;

    return (
        <div className={classes.productsContainer}>
            <h3 className={classes.heading}>
                <FormattedMessage
                    id={'linkedProducts.related'}
                    defaultMessage={'Related Products'}
                />
            </h3>
            <div>{relatedItems}</div>
            <h3 className={classes.heading}>
                <FormattedMessage
                    id={'linkedProducts.upsell'}
                    defaultMessage={'Upsell Products'}
                />
            </h3>
            <div>{upsellItems}</div>
            <h3 className={classes.heading}>
                <FormattedMessage
                    id={'linkedProducts.crosssell'}
                    defaultMessage={'Crosssell Products'}
                />
            </h3>
            <div>{crosssellProducts}</div>
        </div>
    );
};

export default LinkedProducts;
Render a component

File Path: src/components/LinkedProducts/index.js

export {default} from './linkedProducts';
Add a custom component in Product Detail Page

File Path: src/targets/local-intercept.js

const { Targetables } = require('@magento/pwa-buildpack');
module.exports = targets => {
    const targetables = Targetables.using(targets);

    const ProductDetails = targetables.reactComponent(
        '@magento/venia-ui/lib/components/ProductFullDetail/productFullDetail.js'
    );

    const LinkedProducts = ProductDetails.addImport(
        "LinkedProducts from 'example-concept/src/components/LinkedProducts/linkedProducts'"
    );

    ProductDetails
        .insertAfterJSX('<Form />', `<${LinkedProducts} />`)
        .setJSXProps('LinkedProducts', {
            'classes': '{classes}',
            'productDetails': '{productDetails}',
            'options': '{options}',
            'mediaGalleryEntries': '{mediaGalleryEntries}'
        });
};
Run PWA storefront

Execute the following command in the terminal to build and watch your example package:

yarn watch:example

Open the storefront URL and got to the product detail page. You can see the related, upsell, and cross-sell products like the following screenshots:



We hope this blog may understandable and useful to you. You can email us at mage2developer@gmail.com if we missed anything or want to add any suggestions. We will respond to you as soon as possible. Happy to help 🙂