Migrate to inventory actions and useInventory hook

Overview

This is a guide on how to migrate from manually accessing the product inventory to using the new approaches available:

useInventory hook.

The useInventory (Shopify / BigCommerce) hook can be used to retrieve inventory data on demand.

With the useInventory hook, we have access to the following properties:

nametypedescription
availableForSalebooleanIt is used to check whether the product is available for sale.
quantitynumberThe stock quantity of a given product.
pricenumberThe product's price

useCartActions

The following actions are available when using the useCartActions:

  • isProductAvailableForSale
  • getProductQuantity

Before these helpers were available you would access products within the inventory in the following way.

// Old way
import { useCartState } from 'frontend-checkout'

const { inventory } = useCartState()

if (inventory.status === 'loading') return

const variant = inventory.productVariants[variantId]

if (!variant) return

const isAvailableForSale = variant.isAvailableForSale
const quantity = variant.quantity

With the useInventory hook, you can get this information in the following way (check the useInventory-hook-complete-example.js tab below for a more thorough explanation):

import { useInventory } from 'frontend-checkout'

// If you are using Shopify's Storefront api
const id = 'Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0Lzc4NTc5ODkzODQ=' // storefrontId - GraphQL
// If you are using Shopify's REST api
const id = 6690750136476 // externalId - REST

// By passing an id and product type, useInventory will fetch the data for given ids
const { products, status } = useInventory({ ids: [id] })
const { availableForSale, quantity, price } = products[id] || {}
import React, { useState, useEffect } from 'react'
import { useInventory } from 'frontend-checkout'

const IDLE = 'idle'
const LOADING = 'loading'
const SOLD_OUT = 'sold out'
const ERROR = 'error'

const AddToCartButton = ({
  // let's assume storefrontId is coming via props
  // from parent component, say ProductBox
  storefrontId,
}) => {
  // state to deal with button state
  const [buttonState, setButtonState] = useState(storefrontId ? LOADING : ERROR)
  // If you are using Shopify's REST api, you need to provide `externalId`
  // instead of `storefrontId`, and `productType`, something like this:
  // const { product, loading } = useInventory({ ids: [externalId], productType: 'Product' }) 
  const { products, status } = useInventory({ ids: [storefrontId] })
  const { availableForSale } = products[storefrontId] || {}
  
  useEffect(() => {
    if (status === LOADING || !storefrontId) return

    // if a product variant is available for sale,
    // we set the button state to IDLE, we can deal with that later in the code
    if (availableForSale) {
      return setButtonState(IDLE)
    }

    // if any of the above conditions are not true, we can assume that
    // the product is not available for sale, so we set the button state
    // to SOLD_OUT
    setButtonState(SOLD_OUT)

  }, [loading, storefrontId])

  // if the button state in place, feel free to use whatever approach you want
  // to handle the different button states you might have
  // for instance, something like this:
  return (
    <button
      onClick={someAction}
      disabled={buttonState === SOLD_OUT}
    >
      Add to Cart
    </button>
  )
}

With the useCartActions you can use the helpers to get this information from the inventory (check the usecartactions-complete-example.js tab below for a more thorough explanation) :

import { useCartState, useCartActions } from 'frontend-checkout'

const {
  isProductAvailableForSale,
  getProductQuantity,
} = useCartActions()

// variantId can be one of the following:
// From Product.variants for both REST & GraphQL - Shopify
// - REST = LineItem.variant_id
// - GraphQL = LineItem.variant.id
const isAvailableForSale = await isProductAvailableForSale({ id: variantId })
const quantity = await getProductQuantity({ id: variantId })
import React, { useState, useEffect } from 'react'
import { useCartActions } from 'frontend-checkout'

const IDLE = 'idle'
const LOADING = 'loading'
const SOLD_OUT = 'sold out'
const ERROR = 'error'

const AddToCartButton = ({
  // let's assume storefrontId is coming via props
  // from parent component, say ProductBox
  storefrontId,
}) => {
  // state to deal with button state
  const [buttonState, setButtonState] = useState(storefrontId ? LOADING : ERROR)
  const { isProductAvailableForSale } = useCartActions()

  const checkAvailability = async () => {
    const isAvailable = await isProductAvailableForSale(storefrontId)

    // if a product variant is available for sale,
    // we set the button state to IDLE, we can deal with that later in the code
    if (isAvailable)
      return setButtonState(IDLE)
    }

    // if any of the above conditions are not true, we can assume that
    // the product is not available for sale, so we set the button state
    // to SOLD_OUT
    setButtonState(SOLD_OUT)
  }

  useEffect(() => {
    if (!storefrontId) return

    checkAvailability(storefrontId)
  }, [storefrontId])

  // if the button state in place, feel free to use whatever approach you want
  // to handle the different button states you might have
  // for instance, something like this:
  return (
    <button
      onClick={someAction}
      disabled={buttonState === SOLD_OUT}
    >
      Add to Cart
    </button>
  )
}

Comparing approaches

Approach using inventory directly

The cartState.inventory updates using the productInventory.json file generated by the backend, it contains all store products and productVariants and updates once in a minute.

🚧

To remove inventory delay, we are phasing out this approach after all the stores are migrated to one of the new approaches.

Why migrate?

  • By using this approach, we're loading data for all products even if we are on the product page where we need data only for a single product.
  • Data can be outdated, the delay between actual updates in inventory can be up to a minute.
  • We can't change the interval for productInventory.json file loading in the Section code.
  • Not recommended for stores with a large inventory.

Approach using the useInventory hook.

The useInventory hook makes an API request to get the same data but only for those products / productVariants that we need:

  • We load data only for those products that we need.
  • Data updates immediately when it has been changed on the admin.
  • We can set an optional interval that will make requests in the provided period to update data, or don't make it at all.