Adding multiple to cart

Our Storeapi supports a mutation addMultipleToCart that takes a cart ID and a list of products. It will add all of the products to cart if it can, and will return errors for any that failed.

Whilst the mutation can be used directly, the Flight framework comes with a hook that does some of the work for you.

useMultipleAddToCart

Example usage:

Copy
Copied
const [addMultipleToCart, { loading, called, data, error, failedItems }] =
  useAddMultipleToCart(
    items,
    { cartQuery, addMultipleToCartMutation },
    { onCompleted, onError, onPartialFailure }
  );

This hook takes the follow arguments:

Copy
Copied
items: AddMultipleProduct[],
queries: AddMultipleToCartQueries,
callbacks?: UseAddMultipleToCartOption

The type definitions for the arguments are as follows:

Copy
Copied
interface AddMultipleProduct extends Product, AddMultipleToCartInput {}

type AddMultipleToCartMutationResponse = FetchResult<{
  addMultipleToCart: CartMutation;
}>;

interface AddMultipleToCartQueries {
  addMultipleToCartMutation: DocumentNode;
  cartQuery: DocumentNode;
}

interface UseAddMultipleToCartOptions {
  onCompleted?(resp: AddMultipleToCartMutationResponse): any;
  onError?(resp: AddMultipleToCartMutationResponse): any;
  onPartialFailure?(resp: AddMultipleToCartMutationResponse): any;
}

items

The items argument should be an array of products with at minimum an articleNumber and quantity specified.

Each item may contain all normal Product fields, as well as the fields on AddMultipleToCartInput (shown below).

Copy
Copied
type AddMultipleToCartInput {
  articleNumber: String!
  quantity: Int = null
  comments: [InputComment] = null
  configurationIds: [ID] = null
  preOrderDate: DateTime = null
}

So, your items array might look something like this:

Copy
Copied
const items = [
  {
    id: 145,
    primaryRoute: {
      path: "/random-products/with-1-level-attributes/kinky-ninja",
    },
    articleNumber: "1L1VZQ9180",
    hasVariants: false,
    quantity: 2,
  },
  {
    id: 529,
    primaryRoute: {
      path: "/random-products/product-with-percent-discount",
    },
    articleNumber: "AD98D89DA",
    hasVariants: false,
    quantity: 10,
  },
];

queries

The second argument, queries, should be an object containing two keys:

addMultipleToCartMutation

This is the mutation. There is an example of this in Trend at src/components/Cart/addMultipleToCart.gql. This query should return the Cart fragment, which is also used by the cartQuery. Doing this allows the cart to automatically update after the mutation succeeds.

cartQuery

This should be the same query you use to query for your cart. This is needed in order to re-fetch your cart (to update quantities, price, etc) after the MultipleAddToCart mutation runs, in the case of a partial error.

By default in Trend, this query lives at src/components/Cart/queries/cartQuery.gql

callbacks

These are optional callbacks that can be used to add side-effects at different stages of the mutation.

onCompleted(response)

This will be called with the mutation response after the mutation succeeds (including when it succeeds with a partial failure).

onPartialFailure(response)

This will be called with the mutation response when some of the items are not able to be added to cart. The response will include the errors.

onError(response)

This will be called if the entire mutation fails, and no items could be added to cart. The response will include the error.

Return values

The return values are similar to Apollo Client's useMutation, with some added features.

The hook returns an tuple with the mutation as the first entry, and an object containing status flags and data as the second entry (the mutation result).

Copy
Copied
[
  () => Promise<AddMultipleToCartMutationResponse>, // the mutation function
  {
    loading: boolean;
    called: boolean;
    error: boolean;
    data: AddMultipleToCartMutationResponse;
    failedItems: Map<AddMultipleProduct, FailedReason>;
  }
]

Mutate function

This is identical to the mutation function returned by useMutation. Calling this function will fire off the mutation and return a Promise, which will resolve with the response from the API.

Mutation result

The second entry is an object containing the result of the mutation.

loading

Whether or not the mutation is loading (in-flight).

called

Whether or not the mutation has been called at least once.

error

Whether or not the mutation has failed completely.

data

The full response from the API.

failedItems

If some items are added to the cart but others fail, failedItems will include those that failed. failedItems is a Map with each item that failed as the key, and the error code (string) as the value.

TIP: You can easily display the failed products by extracting them from the Map and converting the result to an array:

Copy
Copied
<div>
  <h2>Failed items:</h2>
  {[...failedItems.keys()].map((product) => (
    <div key={product.id + product.quantity}>
      {product.name} {product.articleNumber}
    </div>
  ))}
</div>

You can also determine whether an item has failed inline like this:

Copy
Copied
function Products({ products }) {
  const [mutate, { failedItems }] = useAddMultipleToCart(products, {
    addMultipleToCartMutation,
    cartQuery,
  });

  return products.map((product) => {
    const failedProductCode = failedItems.get(product);
    // will be undefined if the product did not fail

    return (
      <Product
        product={product}
        key={product.articleNumber}
        failCode={failedProductCode}
      />
    );
  });
}

function Product({ product, failCode }) {
  return (
    <div>
      {failCode && `Error code: ${failCode}`}
      <h1 style={failCode ? { color: "red" } : { color: "blue" }}>
        {product.name}
      </h1>
    </div>
  );
}
Copyright © Norce 2023. All right reserved.