New Shopify Certification now available: Liquid Storefronts for Theme Developers

Product SelectedOption attribute when using multilingual

Damin
Visitor
1 0 1

I'm experiencing something weird with the product SelectedOption attribute, it works perfectly if i enter the name and value in arabic however if i use translation and try to get the return in english it always return as null,
exemple 1 (works perfectly) :

query ($id: ID!, $selectedOptions: [SelectedOptionInput!]!) {
product(id: $id) {
variantBySelectedOptions(selectedOptions: $selectedOptions) {
id
}
}
}
Query variables
{
"id": "gid://shopify/Product/6641135026247",
"selectedOptions": [{"name" :"اللون" , "value": "ذهبي"},{ "name": "السعة","value": "128GB"}]
}

Response

{
"data": {
"product": {
"variantBySelectedOptions": {
"id": "gid://shopify/ProductVariant/39625904488519"
}
}
}
}


exemple 2 (always returns null) :

query ($id: ID!, $selectedOptions: [SelectedOptionInput!]!)@inContext(language: EN) {
product(id: $id) {
variantBySelectedOptions(selectedOptions: $selectedOptions) {
id
}
}
}
Query variables
{
"id": "gid://shopify/Product/6641135026247",
"selectedOptions": [{"name" :"Color" , "value": "Gold"},{ "name": "Capicity","value": "128GB"}]
}

Response:

{
"data": {
"product": {
"variantBySelectedOptions": null
}
},
"extensions": {
"context": {
"country": "QA",
"language": "EN"
}
}
}

I've attached 2 screenshots to make it easier to understand the issue, please take a look at them.

 

Perhaps anyone encountered same issue can help me out or provide any tip to fix it i would be really grateful, thanks in advance everyone.

Reply 1 (1)
oscar-peyron
Shopify Partner
1 0 0

I was facing the same issue with the SelectedOption attribute and decided to build a custom solution to address this. Note that the navigation has to be based on the variant id, instead of the selected options.

Feel free to reach out if you need help implementing this.

 

import {useLocation} from '@remix-run/react';
import {flattenConnection, parseGid} from '@shopify/hydrogen';
import {Fragment, ReactNode, createElement, useMemo} from 'react';

export function VariantSelector({
  variants: _variants = {nodes: []},
  options = [],
  selectedVariant,
  handle,
  productPath = 'products',
  children,
}) {
  const {searchParams, path, alreadyOnProductPage} = useVariantPath(
    handle,
    productPath,
  );

  const variants =
    _variants instanceof Array ? _variants : flattenConnection(_variants);

  const selectedOptions = selectedVariant.selectedOptions;

  return createElement(
    Fragment,
    null,
    ...useMemo(() => {
      return options
        .filter((option) => option?.values?.length > 1)
        .map((option) => {
          const values = option.values
            .map((value) => {
              const clonedSearchParams = new URLSearchParams(
                alreadyOnProductPage ? searchParams : void 0,
              );

              const variant = variants.find((variant) =>
                variant.selectedOptions.every((variantOption) => {
                  if (variantOption.name === option.name) {
                    return variantOption.value === value;
                  }

                  return selectedOptions.some(
                    (selectedOption) =>
                      selectedOption.name === variantOption.name &&
                      selectedOption.value === variantOption.value,
                  );
                }),
              );

              if (!variant) return null;

              const {id} = parseGid(variant.id);

              clonedSearchParams.set('variant', id);
              const searchString = '?' + clonedSearchParams.toString();

              const isActive = selectedOptions.some(
                (selectedOption) =>
                  selectedOption.name === option.name &&
                  selectedOption.value === value,
              );

              return {
                value,
                to: path + searchString,
                variant,
                isAvailable: variant ? variant.availableForSale : true,
                isActive,
              };
            })
            .filter(Boolean);

          return children({
            option: {
              name: option.name,
              values,
            },
          });
        });
    }, [options, variants, selectedOptions, children]),
  );
}

 

 

// ($locale).products.$handle.js

export const loader: LoaderFunction = async ({
  params,
  request,
  context,
}: LoaderArgs) => {
  const {handle} = params;
  const {storefront} = context;

  const selectedVariantOption = getSelectedProductOptions(request).find(
    (option) => option.name === 'variant',
  );

...

let selectedVariant;

  // await the query for the critical product data
  const {product, node: selectedVariantNode} = await storefront.query(
    PRODUCT_QUERY,
    {
      variables: {
        handle,
        variantId: toGid(
          ResourceType.ProductVariant,
          // Make sure to pass variantId even if none is present the URL
          selectedVariantOption?.value || 0,
        ),
      },
    },
  );

...

if (firstVariantIsDefault) {
    // If there is only only variant
    selectedVariant = firstVariant;
  } else if (selectedVariantNode) {
    // If there is a variant parameter and the variant query returned a node
    selectedVariant = selectedVariantNode;
  } else {
    // if no variant was returned from the variant query,
    // we redirect to the first variant's url with it's id applied
    return redirectToFirstVariant({
      product,
      request,
    });
  }

...
}
//.../queries/products.js

export const PRODUCT_QUERY = `#graphql
  query Product(
    $country: CountryCode
    $handle: String!
    $language: LanguageCode
    $variantId: ID!
  ) @inContext(country: $country, language: $language) {
    product(handle: $handle) {
      ...Product
    }
    node(id: $variantId){
      ...on ProductVariant{
        ...ProductVariant
      }
    }
  }
  ${PRODUCT_FRAGMENT}
  ${VARIANT_FRAGMENT}
` as const;