Вопрос или проблема
Я использую GraphQL и Apollo с API витрины Shopify.
Я пытаюсь реализовать добавление товара в корзину, и у меня в корзине оказывается два одинаковых товара. Когда у меня пустая корзина и я добавляю товар, в корзине появляются два одинаковых товара. Когда я удаляю их из корзины, оба удаляются, так как у них один и тот же CartLineID.
Я проводил отладку и заметил, что в кэш добавляется один и тот же товар с одним и тем же ID, и я думал, что Apollo сам заботится об этом, поэтому мне интересно, что я делаю не так.
Разве мне не нужно обновлять кэш? Я думал, что для мутаций я должен сам управлять кэшем. Я знаю, что оптимистические ответы автоматически добавляются в кэш, поэтому я запутался в том, что мне делать для обновления кэша. Даже в примере из документации Apollo говорится, что я соединяю существующий список с моим новым товаром. Это имеет смысл, но поскольку оптимистический ответ автоматически добавляет товар, он добавляет его дважды.
Так мне не нужно обновлять кэш или использовать оптимистический ответ для добавления товара?
Может быть, это связано с тем, что я пропустил поле, и он не обнаруживает, что это не тот же ответ, и поэтому не объединяет его должным образом?
Вот мой GraphQL запрос / мутация:
export const FETCH_CART = gql`
query fetchCart($cartId: ID!) {
cart(id: $cartId) {
id
lines(first: 10) {
edges {
node {
id
quantity
merchandise {
... on ProductVariant {
id
image {
url
}
title
price {
amount
currencyCode
}
product {
id
productType
title
}
sku
}
}
}
}
}
totalQuantity
cost {
checkoutChargeAmount {
amount
currencyCode
}
subtotalAmount {
amount
currencyCode
}
subtotalAmountEstimated
totalAmount {
amount
currencyCode
}
totalAmountEstimated
totalDutyAmount {
amount
currencyCode
}
totalDutyAmountEstimated
totalTaxAmount {
amount
currencyCode
}
totalTaxAmountEstimated
}
}
}
`;
export const ADD_TO_CART = gql`
mutation AddCartLine($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart {
id
lines(first: 10) {
edges {
node {
id
quantity
merchandise {
... on ProductVariant {
id
image {
url
}
title
price {
amount
currencyCode
}
product {
id
productType
title
}
sku
}
}
}
}
}
}
}
}
`;
await addCartLine({
variables: {
cartId,
lines: [
{
merchandiseId: newItem.id,
quantity: 1,
},
],
},
optimisticResponse: getOptimisticAddToCartResponse(cartId, {
id: newItem.id,
quantity: 1,
title: newItem.title,
price: newItem.msrp,
currencyCode: 'usd',
url: newItem.feature,
productId: newItem.productId,
productType: newItem.type,
sku: newItem.sku,
variantTitle: newItem.variantTitle,
}),
update(cache, { data: { cartLinesAdd } }) {
const addedLine = cartLinesAdd.cart.lines.edges[0].node; // Предполагаем, что добавляется только одна линия
updateAddToCartCache(cache, cartId, {
id: addedLine.id,
quantity: addedLine.quantity,
title: addedLine.merchandise.title,
price: addedLine.merchandise.price.amount,
currencyCode: addedLine.merchandise.price.currencyCode,
url: addedLine.merchandise.image?.url, // Опциональная цепочка для безопасности
productId: addedLine.merchandise.product.id,
productType: addedLine.merchandise.product.productType,
sku: addedLine.merchandise.sku,
variantId: addedLine.merchandise.id
});
},
});
Мой оптимистичный ответ:
export const getOptimisticAddToCartResponse = (
cartId: string,
newLine: {
id: string;
quantity: number;
title: string;
price: number;
currencyCode: string;
url: string;
productId: string;
productType: string;
sku: string;
variantTitle: string;
}
) => ({
cartLinesAdd: {
cart: {
id: cartId,
lines: {
__typename: 'BaseCartLineConnection',
edges: [
{
__typename: 'BaseCartLineEdge',
node: {
id: `temp-line-${Date.now()}`,
quantity: 1,
merchandise: {
__typename: 'ProductVariant',
id: newLine.id,
image: {
url: newLine.url,
},
title: newLine.variantTitle,
price: {
amount: newLine.price,
currencyCode: newLine.currencyCode,
},
product: {
id: newLine.productId,
productType: newLine.productType,
title: newLine.title,
},
sku: newLine.sku,
},
__typename: 'CartLine',
},
},
],
},
__typename: 'Cart',
},
__typename: 'CartLinesAddPayload',
},
});
Мое обновление кэша при добавлении товара в корзину:
export const updateAddToCartCache = (
cache: ApolloCache<any>,
cartId: string,
newLine: {
id: string;
quantity: number;
title: string;
price: number;
currencyCode: string;
url: string;
productId: string;
productType: string;
sku: string;
variantId: string;
}
) => {
debugger;
// Чтение существующей корзины из кэша
const existingCart = cache.readQuery({
query: FETCH_CART,
variables: { cartId },
});
if (!existingCart) return;
// Добавление новой линии корзины в существующие линии корзины
const updatedLines = [
...existingCart.cart.lines.edges,
{
node: {
id: newLine.id,
quantity: newLine.quantity,
merchandise: {
__typename: 'ProductVariant',
id: newLine.variantId,
image: {
url: newLine.url,
},
title: newLine.title,
price: {
amount: newLine.price,
currencyCode: newLine.currencyCode,
},
product: {
id: newLine.productId,
productType: newLine.productType,
title: newLine.title,
},
sku: newLine.sku,
},
__typename: 'CartLine',
},
__typename: 'BaseCartLineEdge',
},
];
// Запись обновленной корзины обратно в кэш
cache.writeQuery({
query: FETCH_CART,
variables: { cartId },
data: {
cart: {
...existingCart.cart,
lines: {
__typename: 'BaseCartLineConnection',
edges: updatedLines,
},
__typename: 'Cart',
},
},
});
};
Ответ или решение
При реализации добавления товара в корзину с использованием Apollo и GraphQL, возникают некоторые подводные камни, связанные с кэшированием и обработкой оптимистичных ответов. Давайте разберемся с вашим случаем и возможными решениями.
Причина дублирования товаров в корзине
Судя по вашему описанию и коду, причина, по которой товары добавляются в корзину дважды, заключается в том, что вы одновременно используете оптимистичные ответы и явно обновляете кэш. Когда вы вызываете мутацию addCartLine
, Apollo автоматически обновляет кэш с данными, полученными из мутации. Однако в вашем случае вы также обновляете кэш вручную, что приводит к дублированию товаров.
Решение проблемы
-
Избегайте дублирования в кэше: Вам нужно убедиться, что только одно обновление кэша происходит после добавления товара. Если вы используете оптимистичный ответ, обновление кэша в функции
update
должно быть минимальным или отсутствовать, так как оптимистичный ответ уже выполняет добавление в кэш. -
Оптимистичный ответ: Убедитесь, что в оптимистичном ответе вы создаете временный идентификатор для нового товара, чтобы не пересекаться с существующими товарными строками. Это поможет гарантировать, что после окончательного ответа сервера товары не будут склеены.
Пример кода
// Оптимистичный ответ
export const getOptimisticAddToCartResponse = (
cartId: string,
newLine: {
id: string;
quantity: number;
title: string;
price: number;
currencyCode: string;
url: string;
productId: string;
productType: string;
sku: string;
variantTitle: string;
}
) => ({
cartLinesAdd: {
cart: {
id: cartId,
lines: {
__typename: 'BaseCartLineConnection',
edges: [
{
__typename: 'BaseCartLineEdge',
node: {
id: `temp-line-${Date.now()}`, // Уникальный временный ID
quantity: 1,
merchandise: {
__typename: 'ProductVariant',
id: newLine.id,
image: {
url: newLine.url,
},
title: newLine.variantTitle,
price: {
amount: newLine.price,
currencyCode: newLine.currencyCode,
},
product: {
id: newLine.productId,
productType: newLine.productType,
title: newLine.title,
},
sku: newLine.sku,
},
__typename: 'CartLine',
},
},
],
},
__typename: 'Cart',
},
__typename: 'CartLinesAddPayload',
},
});
// Обновление кэша
await addCartLine({
variables: { cartId, lines: [{ merchandiseId: newItem.id, quantity: 1 }] },
optimisticResponse: getOptimisticAddToCartResponse(cartId, {
id: newItem.id,
quantity: 1,
title: newItem.title,
price: newItem.msrp,
currencyCode: 'usd',
url: newItem.feature,
productId: newItem.productId,
productType: newItem.type,
sku: newItem.sku,
variantTitle: newItem.variantTitle,
}),
update(cache, { data: { cartLinesAdd } }) {
const { cart } = cartLinesAdd;
// Обновление кэша только через наглядные линии, не добавляя новые элементы
cache.writeQuery({
query: FETCH_CART,
variables: { cartId },
data: { cart },
});
},
});
Итог
Для успешного добавления товара в корзину:
- Используйте уникальный идентификатор в оптимистичном ответе.
- Минимизируйте обработку кэша в функции обновления, если вы уже используете оптимистичные ответы.
- Убедитесь, что функциональность вашего кода не дублируется, иначе вы столкнетесь с проблемой дублирования товаров.
Рекомендуется провести тестирование после реализации, чтобы точно гарантировать отсутствие дальнейших проблем.