App reviews, troubleshooting, and recommendations
We're moving the community! Starting July 7, the current community will be read-only for approx. 2 weeks. You can browse content, but posting will be temporarily unavailable. Learn more
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>
);
}
Same issue here, even with sample code it is not working for me
Hey @skipper17 were you able to find a solution for this? Facing a similar issue 😞
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.
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] )