Skip to content

Conversation

@jorgemoya
Copy link
Contributor

@jorgemoya jorgemoya commented Jan 8, 2026

What/Why?

Persist the checkbox product modifier since it can modify pricing and other product data. By persisting this and tracking in the url, this will trigger a product refetch when added or removed. Incidentally, now we manually control what fields are persisted, since option.isVariantOption doesn't apply to checkbox, additionally multi options modifiers that are not variant options can also modify price and other product data.

Testing

Without insurance:
Screenshot 2026-01-08 at 3 03 16 PM

With insurance (insurance is $100 in this example):
Screenshot 2026-01-08 at 3 03 28 PM

Migration

Step 1

Update product-options-transformer.ts to manually track persisted fields:

case 'DropdownList': {
    return {
        // before
        // persist: option.isVariantOption,
        // after (manually persist)
        persist: true,
        type: 'select',
        label: option.displayName,
        required: option.isRequired,
        name: option.entityId.toString(),
        defaultValue: values.find((value) => value.isDefault)?.entityId.toString(),
        options: values.map((value) => ({
        label: value.label,
        value: value.entityId.toString(),
        })),
    };
}

Fields that persist and can affect product pricing when selected:

  • Swatch
  • RectangleBoxes
  • RadioButtons
  • ProductPickList
  • ProductPickListWithImages
  • CheckboxOption

Step 2

Remove isVariantOption from GQL query since we no longer use it:

export const ProductOptionsFragment = graphql(
  `
    fragment ProductOptionsFragment on Product {
      entityId
      productOptions(first: 50) {
        edges {
          node {
            __typename
            entityId
            displayName
            isRequired
            isVariantOption // remove this
            ...MultipleChoiceFieldFragment
            ...CheckboxFieldFragment
            ...NumberFieldFragment
            ...TextFieldFragment
            ...MultiLineTextFieldFragment
            ...DateFieldFragment
          }
        }
      }
    }
  `,
  [
    MultipleChoiceFieldFragment,
    CheckboxFieldFragment,
    NumberFieldFragment,
    TextFieldFragment,
    MultiLineTextFieldFragment,
    DateFieldFragment,
  ],
);

Step 3

Update product-detail-form.tsx to include separate handing of the checkbox field:

const defaultValue = fields.reduce<{
  [Key in keyof SchemaRawShape]?: z.infer<SchemaRawShape[Key]>;
}>(
  (acc, field) => {
    // Checkbox field has to be handled separately because we want to convert checked or unchecked value to true or undefined respectively.
    // This is because the form expects a boolean value, but we want to store the checked or unchecked value in the query params.
    if (field.type === 'checkbox') {
      if (params[field.name] === field.checkedValue) {
        return {
          ...acc,
          [field.name]: 'true',
        };
      }

      if (params[field.name] === field.uncheckedValue) {
        return {
          ...acc,
          [field.name]: undefined,
        };
      }

      return {
        ...acc,
        [field.name]: field.defaultValue, // Default value is either 'true' or undefined
      };
    }

    return {
      ...acc,
      [field.name]: params[field.name] ?? field.defaultValue,
    };
  },
  { quantity: minQuantity ?? 1 },
);

...

const handleChange = useCallback(
  (value: string) => {
    // Checkbox field has to be handled separately because we want to convert 'true' or '' to the checked or unchecked value respectively.
    if (field.type === 'checkbox') {
      void setParams({ [field.name]: value ? field.checkedValue : field.uncheckedValue });
    } else {
      void setParams({ [field.name]: value || null }); // Passing `null` to remove the value from the query params if fieldValue is falsey
    }

    controls.change(value || ''); // If fieldValue is falsey, we set it to an empty string
  },
  [setParams, field, controls],
);

Step 4

Update schema in core/vibes/soul/sections/product-detail/schema.ts:

type CheckboxField = {
  type: 'checkbox';
  defaultValue?: string;
  checkedValue: string; // add
  uncheckedValue: string; // add
} & FormField;

@jorgemoya jorgemoya requested a review from a team as a code owner January 8, 2026 21:06
@changeset-bot
Copy link

changeset-bot bot commented Jan 8, 2026

🦋 Changeset detected

Latest commit: 0a83e9f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@bigcommerce/catalyst-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Jan 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
catalyst Ready Ready Preview, Comment Jan 9, 2026 9:57pm

@jorgemoya jorgemoya force-pushed the catalyst-1396-track-modifiers-pdp branch from 10a13d9 to d6764ac Compare January 8, 2026 21:08
Comment on lines -336 to -337
// Ensure that if page is reached without a full reload, we are still setting the selection properly based on query params.
const fieldValue = value || params[field.name];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified this is no longer needed.

Copy link
Contributor

@chanceaclark chanceaclark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love it when the changesets is more code than the actual changes ❤️

@jorgemoya jorgemoya force-pushed the catalyst-1396-track-modifiers-pdp branch from 878b66a to 0a83e9f Compare January 9, 2026 21:55
@jorgemoya jorgemoya added this pull request to the merge queue Jan 9, 2026
Merged via the queue into canary with commit d469078 Jan 9, 2026
8 checks passed
@jorgemoya jorgemoya deleted the catalyst-1396-track-modifiers-pdp branch January 9, 2026 22:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants