Skip to content

Conversation

@fredericoo
Copy link
Contributor

@fredericoo fredericoo commented Jan 22, 2026

Closes #969

#3276

WHY are these changes introduced?

This PR adds the ability to display warnings to users in the cart, improving the user experience by providing clear feedback when there are issues with their cart.

WHAT is this pull request doing?

  • Adds a new InlineWarning component for displaying warning messages in an accessible way
  • Implements useCartFeedback hook to collect and normalize errors and warnings from cart fetchers
  • Adds CartWarnings component to display warnings in the cart interface
  • Updates the cart styling to properly display warning messages
  • Integrates the warning system with the existing cart functionality

HOW to test your changes?

  1. Add items to cart
  2. Trigger a cart warning (e.g., by attempting to add more items than available in inventory)
  3. Verify that warnings appear properly in the cart interface
  4. Check that warnings are properly styled and accessible

Checklist

  • I've read the Contributing Guidelines
  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've added a changeset if this PR contains user-facing or noteworthy changes
  • I've added tests to cover my changes
  • I've added or updated the documentation

Copy link
Contributor Author

fredericoo commented Jan 22, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@fredericoo fredericoo changed the title fix: adjust state Add MERCHANDISE_SELLING_PLAN_NOT_APPLICABLE_ON_COMPANY_LOCATION warning support Jan 22, 2026
@shopify
Copy link
Contributor

shopify bot commented Jan 22, 2026

Oxygen deployed a preview of your Add_MERCHANDISE_SELLING_PLAN_NOT_APPLICABLE_ON_COMPANY_LOCATION_warning_support branch. Details:

Storefront Status Preview link Deployment details Last update (UTC)
Skeleton (skeleton.hydrogen.shop) ✅ Successful (Logs) Preview deployment Inspect deployment January 28, 2026 6:05 PM
metaobjects ✅ Successful (Logs) Preview deployment Inspect deployment January 23, 2026 4:06 PM
custom-cart-method ✅ Successful (Logs) Preview deployment Inspect deployment January 23, 2026 4:06 PM
third-party-queries-caching ✅ Successful (Logs) Preview deployment Inspect deployment January 23, 2026 4:06 PM
sitemap ✅ Successful (Logs) Preview deployment Inspect deployment January 23, 2026 4:06 PM

Learn more about Hydrogen's GitHub integration.

@fredericoo fredericoo force-pushed the Add_MERCHANDISE_SELLING_PLAN_NOT_APPLICABLE_ON_COMPANY_LOCATION_warning_support branch from 3ecb1f8 to 9a80d6f Compare January 22, 2026 20:09
@fredericoo fredericoo marked this pull request as ready for review January 22, 2026 20:11
@fredericoo fredericoo requested a review from a team as a code owner January 22, 2026 20:11
@kdaviduik kdaviduik changed the base branch from 2025-10-sfapi-caapi-update to graphite-base/3400 January 23, 2026 03:59
@fredericoo fredericoo force-pushed the Add_MERCHANDISE_SELLING_PLAN_NOT_APPLICABLE_ON_COMPANY_LOCATION_warning_support branch from 9a80d6f to 955ac0f Compare January 23, 2026 16:03
@fredericoo fredericoo changed the base branch from graphite-base/3400 to 2025-10-sfapi-caapi-update January 23, 2026 16:03
@fredericoo fredericoo changed the title Add MERCHANDISE_SELLING_PLAN_NOT_APPLICABLE_ON_COMPANY_LOCATION warning support Add cart warnings component to display feedback to users Jan 23, 2026
@kdaviduik kdaviduik changed the base branch from 2025-10-sfapi-caapi-update to graphite-base/3400 January 26, 2026 03:28
@fredericoo fredericoo force-pushed the Add_MERCHANDISE_SELLING_PLAN_NOT_APPLICABLE_ON_COMPANY_LOCATION_warning_support branch 2 times, most recently from 80251b9 to 98b056a Compare January 26, 2026 13:55
@fredericoo fredericoo changed the base branch from graphite-base/3400 to 2025-10-sfapi-caapi-update January 26, 2026 13:55
@fredericoo fredericoo force-pushed the Add_MERCHANDISE_SELLING_PLAN_NOT_APPLICABLE_ON_COMPANY_LOCATION_warning_support branch from 98b056a to 4c316a0 Compare January 26, 2026 14:53
@fredericoo fredericoo changed the title Add cart warnings component to display feedback to users Display warnings and errors in the cart Jan 26, 2026
@fredericoo fredericoo force-pushed the Add_MERCHANDISE_SELLING_PLAN_NOT_APPLICABLE_ON_COMPANY_LOCATION_warning_support branch from 4c316a0 to 75e10cd Compare January 26, 2026 15:25
@kdaviduik kdaviduik changed the base branch from 2025-10-sfapi-caapi-update to graphite-base/3400 January 26, 2026 19:37
Copy link
Contributor

Please re-request my review when you want me to take a look again :) I saw you made some changes but this comment still applies so I'm assuming it's not yet ready for re-review

image.png

@fredericoo fredericoo force-pushed the Add_MERCHANDISE_SELLING_PLAN_NOT_APPLICABLE_ON_COMPANY_LOCATION_warning_support branch 2 times, most recently from 66385cf to 2ead87a Compare January 27, 2026 11:10
@fredericoo fredericoo changed the base branch from graphite-base/3400 to 2025-10-sfapi-caapi-update January 27, 2026 11:10
Base automatically changed from 2025-10-sfapi-caapi-update to main January 27, 2026 17:08
@fredericoo fredericoo force-pushed the Add_MERCHANDISE_SELLING_PLAN_NOT_APPLICABLE_ON_COMPANY_LOCATION_warning_support branch from 09b9d0e to cdab032 Compare January 28, 2026 18:03
>(new Map());
const fetchers = useFetchers();

useEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

fetcherDataMap is in the dependency array but also being updated inside this effect. In the 'submitting' case, changed is unconditionally set to true and a new empty Map is created every time. Since new Map() !== new Map() (reference inequality), React sees this as a state change even though both are empty.

The loop is prevented by the fetcher transitioning to 'loading' state quickly, not by the changed flag. This makes the code fragile — it relies on timing behaviour of React Router rather than explicit loop protection.

The 'loading' case is safer because the data comparison (fetcher.data !== newFetcherDataMap.get(fetcher.key)) naturally terminates the loop once data is stored.

Consider using the functional update pattern instead:

useEffect(() => {
  setFetcherDataMap((prevMap) => {
    let changed = false;
    const newMap = new Map(prevMap);

    for (const fetcher of fetchers) {
      if (!isCartFetcher(fetcher)) continue;

      switch (fetcher.state) {
        case 'submitting':
          if (prevMap.size > 0) {
            return new Map(); // Clear on new submission
          }
          break;
        case 'loading':
          if (fetcher.data && fetcher.data !== prevMap.get(fetcher.key)) {
            changed = true;
            newMap.set(fetcher.key, fetcher.data);
          }
          break;
      }
    }

    return changed ? newMap : prevMap;
  });
}, [fetchers]); // fetcherDataMap removed from deps

}
}
return {
errors: Array.from(feedback.errors.values()),
Copy link
Contributor

Choose a reason for hiding this comment

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

The useCartFeedback hook returns errors (line 80), but the CartWarnings component only renders warnings and userErrors.

Is this intentional? If errors shouldn't display here, consider removing them from the hook's return value to avoid confusion. If they should display, we're missing that rendering.


/**
* An accessible inline feedback component for warnings and errors.
* Uses role="alert" to announce changes to assistive technology.
Copy link
Contributor

Choose a reason for hiding this comment

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

JSDoc doesn't match implementation

The comment says:

Uses role="alert" to announce changes to assistive technology.

But line 19 uses role="status".

Either update the comment to match, or consider making the role conditional:

role={type === 'error' ? 'alert' : 'status'}

Previously I had proposed using `role="status"` but I looked into this more and apparently "alert" is preferred for error messages, whereas "status" is preferred for non-error feedback (eg: cart warnings)

fetcher: FetcherWithComponents<any>,
line: CartLine,
) {
const value = e.target.valueAsNumber;
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing NaN validation

e.target.valueAsNumber returns NaN if the input is empty or invalid. This would submit a cart update with quantity: NaN.

async function submitQuantity(
  e: React.ChangeEvent<HTMLInputElement>,
  fetcher: FetcherWithComponents<any>,
  line: CartLine,
) {
  const value = e.target.valueAsNumber;
  if (Number.isNaN(value) || value < 1) {
    return; // Don't submit invalid quantities
  }
  // ... rest
}


async function submitQuantity(
e: React.ChangeEvent<HTMLInputElement>,
fetcher: FetcherWithComponents<any>,
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not strictly enforced right now, but I think we should never add any anys into the codebase
This could be properly typed using the cart action type:

import type {action as cartAction} from '~/routes/cart';
type CartActionResponse = Awaited<ReturnType<typeof cartAction>>;

fetcher: FetcherWithComponents<CartActionResponse>

type="number"
defaultValue={line.quantity}
onChange={(e) => {
if (isKeyboardEvent(e)) return;
Copy link
Contributor

Choose a reason for hiding this comment

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

If a user changes quantity via up/down arrows (triggering onChange), then clicks away (triggering onBlur), both handlers submit the same value.

The fetcher key deduplication mitigates this, but it's still unnecessary network traffic. Consider tracking whether a pending change exists:

onChange={(e) => {
  if (isKeyboardEvent(e)) return; // Keyboard waits for blur
  void submitQuantity(e, fetcher, line);
}}
onBlur={(e) => {
  // Only submit if there's a pending keyboard change
  // Current implementation submits unconditionally
  void submitQuantity(e, fetcher, line);
}}

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