In this section, we will be discussing how to use composite product data and mutations. We will be building on the code written in previous sections of the documentation, specifically the sections on using product data and handling user session and using cart mutations.
Composite products are a unique type of product in WooCommerce that allow store owners to build complex products by combining simple products. These products are designed to manage and provide a lot of visual context data, like the behavior for displaying certain parts of components or totals. It's up to the demands of the store and client application how much of this context should be used.
When adding a composite product to the cart, all components must be provided to the AddCompositeToCart
's configuration
field, even optional components.
Here's the component we'll be using in the examples ahead.
import React from 'react'; import { useQuery } from '@apollo/client'; import { GetProduct } from './graphql'; import useCartMutations from './useCartMutations'; const CompositeProduct = ({ productId }) => { const [quantity, setQuantity] = React.useState(1); const { data, loading, error } = useQuery(GetProduct, { variables: { id: productId, idType: 'DATABASE_ID' }, }); const { quantityInCart: inCart, mutate, loading: cartLoading } = useCartMutations(productId); React.useEffect(() => { if (inCart) { setQuantity(inCart); } }, [inCart]); if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error.message}</p>; const product = data.product; const handleAddOrUpdateAction = async () => { mutate({ quantity }); }; const handleRemoveAction = async () => { mutate({ mutation: 'remove', quantity: 0 }); }; const buttonText = inCart ? 'Update' : 'Add To Cart'; return ( <div className="composite-product"> <h1>{product.name}</h1> <div dangerouslySetInnerHTML={{ __html: product.description }}></div> <p> {product.onSale && <del>{product.regularPrice}</del>} {product.price} </p> <div className="attributes"> {product.attributes.nodes.map((attr) => ( <div key={attr.id}> <strong>{attr.name}:</strong> {attr.options.join(', ')} </div> ))} </div> <div className="cart-options"> {!product.soldIndividually && ( <div className="quantity"> <label htmlFor="quantity">Quantity:</label> <input type="number" id="quantity" name="quantity" min="1" defaultValue={inCart ? inCart : 1} onChange={(event) => setQuantity(Number.parseInt(event.target.value))} /> </div> )} {product.stockStatus === 'IN_STOCK' ? ( <> <button type="button" className="add-to-cart" onClick={handleAddOrUpdateAction} disabled={cartLoading} > {buttonText} </button> {inCart && ( <button type="button" className="remove-from-cart" onClick={handleRemoveAction} disabled={cartLoading} > Remove </button> )} </> ) : ( <p>Out of stock</p> )} </div> </div> ); }; export default CompositeProduct;
This CompositeProduct
component receives a productId
as a prop. It uses the useQuery
hook from Apollo Client to fetch the product data from the GraphQL API. The product data includes the product's name, description, price, and attributes.
The useCartMutations
hook is used to manage the cart actions. It returns the quantity of the item currently in the cart, a mutate
function that can be used to add, update, or remove items, and a loading
flag indicating whether any cart mutations are in progress.
The handleAddOrUpdateAction
and handleRemoveAction
functions call the mutate
function returned by useCartMutations
. The loading
flag is used to disable the buttons while any cart mutations are in progress.
The component renders the product information and provides buttons to add, update, or remove the item from the cart. The "Add to Cart" button's text changes to "Update" if the item is already in the cart. If the product is out of stock, a message is displayed instead of the cart options.
This component is a good starting point. You can further customize and extend it to suit your specific needs.
Alright, let's start by updating the SingleProduct
component to support composite products. We'll use the CompositeCard
component code provided, but we'll rename it to CompositeProduct
for consistency.
Here's the updated SingleProduct
component:
import React from 'react'; import { CompositeProduct } from './CompositeProduct'; import { Product as ProductType } from '@axis/graphql'; export const SingleProduct = ({ product }) => { if (product.__typename === 'CompositeProduct') { return <CompositeProduct product={product} />; } // ...rest of the component };
In the code above, we're checking if the product type is CompositeProduct
. If it is, we're rendering the CompositeProduct
component. If it's not, we're rendering the rest of the SingleProduct
component as usual.
Next, let's update the useCartMutations
hook to add support for addCompositeToCart
. Here's the updated hook:
import { useEffect, useMemo, useState } from 'react'; import { useSession } from '@axis/components/SessionProvider'; import { useAddToCartMutation, useAddCompositeToCartMutation, useUpdateCartItemQuantitiesMutation, useRemoveItemsFromCartMutation, Cart, GetCartDocument, CartItem, } from '@axis/graphql'; export interface CartMutationCompositeInput extends CartMutationInput { configuration: { componentId: string; productId?: number; hidden?: boolean; quantity?: number; variation?: { attributeName: string; attributeValue: string; }[] variationId?: number; }[]; } // ...rest of the hook const useCartMutations = ( productId: number, variationId?: number, extraData?: string, ) => { // ...rest of the hook const [addCompositeToCart, { loading: addingComposite }] = useAddCompositeToCartMutation({ onCompleted({ addCompositeToCart: data }) { if (data?.cart) { setCart(data.cart as Cart); } }, notifyOnNetworkStatusChange: true, }); // ...rest of the hook async function mutate(values) { const { quantity = 1, all = false, mutation = 'update', } = values; if (!cart) { return; } if (!productId) { throw new Error('No item provided.'); // TODO: Send error to Sentry.IO. } let item: CartItem|undefined; switch (mutation) { // ...rest of the switch statement case 'addComposite': if (!values.configuration) { throw new Error('No component configurations provided'); } addCompositeToCart({ variables: { productId, quantity, configuration: values.configuration, extraData, }, }); break; // ...rest of the switch statement } } // ...rest of the hook return store; }; export default useCartMutations;
In the updated hook, we've added a new mutation addCompositeToCart
and a new case in the mutate
function to handle adding composite products to the cart.
Now before finishing up, let's revisit the facts touched upon at the started of section:
-
Composite products are designed to manage and provide a lot of visual context data, like the behavior for displaying certain parts of components or totals. It's up to the demands of the store and client application how much of this context should be used. Some specific GraphQL fields that provide this optional context are the
CompositeProduct
type'saddToCartFormLocation
field, theCompositeProductComponent
type'soptionsStyle
andpaginationStyle
. -
All components must be provided to the
AddCompositeToCart
'sconfiguration
field, even optional components. Optional components should be set with a quantity of0
.
By following these steps and understanding these facts, you can effectively use composite products in your WooCommerce store with GraphQL.
In this section, we have delved into the intricacies of working with Composite Product Data and Mutations. We have demonstrated how to adapt the code from the Using Product Data
and Creating Session Provider and using Cart Mutations
sections to handle the unique specifications of composite products.
We've explored how to modify the ProductListing
and SingleProduct
components to support CompositeProduct
types. We've also shown how to use the addToCart
mutation to add composite products to the cart, taking into account the unique structure of these products.
This exploration has highlighted the flexibility and power of WooGraphQL, demonstrating how it can be used to handle a wide range of product types in a WooCommerce store.
As we wrap up this section, we hope that you now feel confident in your ability to work with composite product data and mutations. The skills and knowledge you've gained here will be invaluable as you continue to build and enhance your headless WooCommerce applications.
In the upcoming sections, we will continue to explore other product types, including Product Bundles and Product Add-ons. Each of these product types presents its own unique challenges and opportunities, and we look forward to guiding you through them.
As always, we encourage you to experiment with the concepts and code snippets provided in this section, applying them to your own projects. Happy coding!