Checkout UI extension line item is not updating properly

skipper17
Shopify Partner
6 0 3

Recently ive started working on shopify apps rights now i am developing a checkout ui extension which lets the merchant to upsell products in checkout page. Last time ive checked everything was working fine but today iam facing some issues while i add up sell products to the cart line items when i add a product it was supposed to update the cart with updated line items but it works as expected when i click on add product 1st time the second time when i click it was supposed to check whether the product is already added in cart if yes it should only update the quantity + 1 this was working fine last week but today its not working and also if i add a new product its not appended to the line items instead it just simply replaces the product which was previously added.

 

documentation: https://shopify.dev/apps/checkout/product-offers/add-product-offer

 

 

App.jsx

import {useEffect, useState} from 'react';
import {
  BlockStack,
  useShop,
  useCartLines,
  useApplyCartLinesChange,
} from '@shopify/checkout-ui-extensions-react';

import {getRecommendations} from './services/recommendations.service.js';
import Loader from './components/Loader.jsx';
import {Divider, Heading} from '@shopify/checkout-ui-extensions';
import ProductCard from './components/ProductCard.jsx';

function App() {
  const storeUrl = useShop().myshopifyDomain;
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(false);
  const lineItems = useCartLines();
  const applyCartLinesChange = useApplyCartLinesChange();
  const recommendationLimit = 2;
  console.log(lineItems, 'line items');
  const lineItemsId = lineItems.map((l) =>
    l.merchandise.product.id.substring(
      l.merchandise.product.id.lastIndexOf('/') + 1
    )
  );

  useEffect(async () => {
    try {
      setLoading(true);
      let resp = await getRecommendations(
        lineItemsId.toString(),
        recommendationLimit,
        storeUrl
      );
      setProducts(resp.data);
      setLoading(false);
      console.log(products, 'api response');
    } catch (error) {
      console.error(error);
    }
  }, []);

  if (loading) return <Loader />;
  if (!loading && products.length === 0) {
    return null;
  }
  return (
    <BlockStack spacing="loose">
      <Divider />
      <Heading level={2}>You might also like</Heading>
      {products.map((p) => (
        <ProductCard
          key={p.id}
          data={p}
          applyCartLinesChange={applyCartLinesChange}
        />
      ))}
    </BlockStack>
  );
}

export default App;


<------------------------------------------->

Product card component

import {useEffect, useState} from 'react';
import {
  Image,
  Button,
  InlineLayout,
  BlockStack,
  Text,
  Banner,
  useExtensionApi,
  Select,
} from '@shopify/checkout-ui-extensions-react';

export default function ProductCard({data, applyCartLinesChange}) {
  const {i18n} = useExtensionApi();
  const [adding, setAdding] = useState(false);
  const [selectedVariant, setSelectedVariant] = useState(0);
  const [showError, setShowError] = useState(false);
  const hasVariants = data.variants.length > 1;
  useEffect(() => {
    if (showError) {
      const timer = setTimeout(() => setShowError(false), 3000);
      return () => clearTimeout(timer);
    }
  }, [showError]);

  function handelSelect(e) {
    setSelectedVariant(e);
  }

  return (
    <BlockStack spacing="loose">
      <InlineLayout
        spacing="base"
        columns={[100, 'fill', 'auto']}
        blockAlignment="center"
      >
        <Image
          border="base"
          borderWidth="base"
          borderRadius="loose"
          source={data.image.src}
          description={data.title}
          aspectRatio={1}
        />
        <BlockStack spacing="none">
          <Text size="medium" emphasis="strong">
            {data.title}
          </Text>
          {hasVariants && (
            <InlineLayout columns={[150, 'auto', 'auto']}>
              <Select
                label="Variant"
                value={selectedVariant}
                onChange={(e) => handelSelect(e)}
                options={data.variants.map((val, i) => ({
                  value: i,
                  label: val.title.replace(/(<([^>]+)>)/gi, ''),
                }))}
              />
            </InlineLayout>
          )}
          <Text appearance="subdued">
            {i18n.formatCurrency(data.variants[selectedVariant].price)}
          </Text>
        </BlockStack>
        <Button
          kind="secondary"
          loading={adding}
          accessibilityLabel={`Add ${data.title} to cart`}
          onPress={async () => {
            setAdding(true);
            // Apply the cart lines change
            const result = await applyCartLinesChange({
              type: 'addCartLine',
              merchandiseId: `gid://shopify/ProductVariant/${data.variants[selectedVariant].id}`,
              quantity: 1,
            });
            setAdding(false);
            if (result.type === 'error') {
              setShowError(true);
              console.error(result.message);
            }
          }}
        >
          Add
        </Button>
      </InlineLayout>
      {showError && (
        <Banner status="critical">
          There was an issue adding this product. Please try again.
        </Banner>
      )}
    </BlockStack>
  );
}

 

Replies 4 (4)

twen7y0ne
Shopify Partner
4 0 0

Same issue here, even with sample code it is not working for me

ruby-roundhouse
Shopify Partner
1 0 0

Hey @skipper17 were you able to find a solution for this? Facing a similar issue 😞

TheodorFreundHT
Shopify Partner
10 0 0

I can confirm the issue.

 

const cartLines = useCartLines()
const lineChanger = useApplyCartLinesChange()
useEffect( () => {
  if (!cartLines) return
  lineChanger({
                type: 'removeCartLine',
                id: cartLines [0].id,
                quantity: cartLines [0].quantity
              })}
}, [cartLines])  

 

This will not as expected empty the whole cart, but only run once, because the subscription does not trigger as one would expect.
Maybe @Shopify could give us an Idea of how these two interact.

I tested this and the checkout page updates the contents of the cart, but does not retrigger this.

TheodorFreundHT
Shopify Partner
10 0 0

Sooo,
I found the issue and a workaround:

 

The problem is that it seems they both run in the same step (react hooks) and the update is not set properly this way. My hotfix was to just create a dummy state to handle this:

const cartLines = useCartLines()
const lineChanger = useApplyCartLinesChange()
// we create a dummy State variable to update everything when we want to
const [ useless, setUseless ] = useState(0)
useEffect( () => { // do something with cartLines here }
  , [useless]) // will trigger on any change to the dummy

// for example, this will empty the cart one by one, reloading once inbetween

useEffect( () => {
  lineChanger( { /*change*/ }).then( () => {
    setUseless( (i) => i+1 ) // set the useless State so the component will update
  })
}, [useless] )