diff --git a/assets/js/base/components/cart-checkout/shipping-calculator/index.tsx b/assets/js/base/components/cart-checkout/shipping-calculator/index.tsx
index 108fb33f25a..204596bffd0 100644
--- a/assets/js/base/components/cart-checkout/shipping-calculator/index.tsx
+++ b/assets/js/base/components/cart-checkout/shipping-calculator/index.tsx
@@ -30,10 +30,9 @@ const ShippingCalculator = ( {
addressFields = [ 'country', 'state', 'city', 'postcode' ],
}: ShippingCalculatorProps ): JSX.Element => {
const { shippingAddress } = useCustomerData();
- const noticeContext = 'wc/cart/shipping-calculator';
return (
-
+
-
-
+
{ /* SlotFillProvider need to be defined before CheckoutProvider so fills have the SlotFill context ready when they mount. */ }
diff --git a/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/block.tsx b/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/block.tsx
index 3d42b8ebc6a..1d372046da0 100644
--- a/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/block.tsx
+++ b/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/block.tsx
@@ -88,10 +88,13 @@ const Block = ( {
] ) as Record< keyof AddressFields, Partial< AddressField > >;
const AddressFormWrapperComponent = isEditor ? Noninteractive : Fragment;
+ const noticeContext = useBillingAsShipping
+ ? [ noticeContexts.BILLING_ADDRESS, noticeContexts.SHIPPING_ADDRESS ]
+ : [ noticeContexts.BILLING_ADDRESS ];
return (
-
+
>;
const AddressFormWrapperComponent = isEditor ? Noninteractive : Fragment;
+ const noticeContext = useShippingAsBilling
+ ? [ noticeContexts.SHIPPING_ADDRESS, noticeContexts.BILLING_ADDRESS ]
+ : [ noticeContexts.SHIPPING_ADDRESS ];
return (
<>
-
+
{
};
```
+#### Multiple contexts
+
+```jsx
+import { StoreNoticesContainer } from '@woocommerce/blocks-checkout';
+
+const AddressForm = () => {
+ return (
+
+ );
+};
+```
+
## Snackbar notices in WooCommerce Blocks
WooCommerce Blocks also shows snackbar notices, to add a snackbar notice you need to create a notice with `type:snackbar` in the options object.
@@ -53,17 +73,3 @@ dispatch( 'core/notices' ).createNotice(
'snackbar-notice-id'
);
```
-
-### `SnackbarNoticesContainer`
-
-To display snackbar notices, use the `SnackbarNoticesContainer` component. This component is rendered with the Cart and Checkout blocks, so there is no need to add another. The context it displays notices for is `default`. If, for some reason you do need to show snackbar messages for a different context, you can render this component again and pass the context as a prop to the component.
-
-```jsx
-import { SnackbarNoticesContainer } from '@woocommerce/base-components/snackbar-notices-container';
-
-const AlternativeSnackbarNotices = () => {
- return (
-
- );
-};
-```
diff --git a/packages/checkout/components/store-notices-container/index.tsx b/packages/checkout/components/store-notices-container/index.tsx
index 08178df360d..65ce1caa7d4 100644
--- a/packages/checkout/components/store-notices-container/index.tsx
+++ b/packages/checkout/components/store-notices-container/index.tsx
@@ -38,13 +38,14 @@ const StoreNoticesContainer = ( {
).getRegisteredContainers(),
} )
);
-
+ const contexts = Array.isArray( context ) ? context : [ context ];
// Find sub-contexts that have not been registered. We will show notices from those contexts here too.
const allContexts = getNoticeContexts();
const unregisteredSubContexts = allContexts.filter(
( subContext: string ) =>
- subContext.includes( context + '/' ) &&
- ! registeredContainers.includes( subContext )
+ contexts.some( ( _context: string ) =>
+ subContext.includes( _context + '/' )
+ ) && ! registeredContainers.includes( subContext )
);
// Get notices from the current context and any sub-contexts and append the name of the context to the notice
@@ -56,13 +57,14 @@ const StoreNoticesContainer = ( {
...unregisteredSubContexts.flatMap( ( subContext: string ) =>
formatNotices( getNotices( subContext ), subContext )
),
- ...formatNotices(
- getNotices( context ).concat( additionalNotices ),
- context
+ ...contexts.flatMap( ( subContext: string ) =>
+ formatNotices(
+ getNotices( subContext ).concat( additionalNotices ),
+ subContext
+ )
),
].filter( Boolean ) as StoreNotice[];
} );
-
if ( suppressNotices || ! notices.length ) {
return null;
}
@@ -71,7 +73,7 @@ const StoreNoticesContainer = ( {
<>
notice.type === 'default'
) }
diff --git a/packages/checkout/components/store-notices-container/store-notices.tsx b/packages/checkout/components/store-notices-container/store-notices.tsx
index 8262ff18764..7957e1d3822 100644
--- a/packages/checkout/components/store-notices-container/store-notices.tsx
+++ b/packages/checkout/components/store-notices-container/store-notices.tsx
@@ -21,7 +21,7 @@ const StoreNotices = ( {
className,
notices,
}: {
- context: string;
+ context: string | string[];
className: string;
notices: StoreNotice[];
} ): JSX.Element => {
@@ -64,12 +64,12 @@ const StoreNotices = ( {
} );
}
}, [ noticeIds, previousNoticeIds, ref ] );
-
// Register the container context with the parent.
useEffect( () => {
- registerContainer( context );
+ const contexts = Array.isArray( context ) ? context : [ context ];
+ contexts.map( ( _context ) => registerContainer( _context ) );
return () => {
- unregisterContainer( context );
+ contexts.map( ( _context ) => unregisterContainer( _context ) );
};
}, [ context, registerContainer, unregisterContainer ] );
@@ -117,6 +117,17 @@ const StoreNotices = ( {
if ( ! noticeGroup.length ) {
return null;
}
+ const uniqueNotices = noticeGroup.filter(
+ (
+ notice: Notice,
+ noticeIndex: number,
+ noticesArray: Notice[]
+ ) =>
+ noticesArray.findIndex(
+ ( _notice: Notice ) =>
+ _notice.content === notice.content
+ ) === noticeIndex
+ );
return (
- { noticeGroup.length === 1 ? (
+ { uniqueNotices.length === 1 ? (
<>
{ sanitizeHTML(
decodeEntities(
@@ -140,7 +151,7 @@ const StoreNotices = ( {
>
) : (
- { noticeGroup.map( ( notice ) => (
+ { uniqueNotices.map( ( notice ) => (
- {
)
);
} );
+
+ it( 'Shows notices from several contexts', async () => {
+ dispatch( noticesStore ).createErrorNotice( 'Custom shipping error', {
+ id: 'custom-subcontext-test-error',
+ context: 'wc/checkout/shipping-address',
+ } );
+ dispatch( noticesStore ).createErrorNotice( 'Custom billing error', {
+ id: 'custom-subcontext-test-error',
+ context: 'wc/checkout/billing-address',
+ } );
+ render(
+
+ );
+ // This should match against 4 elements; A written and spoken message for each error.
+ expect( screen.getAllByText( /Custom shipping error/i ) ).toHaveLength(
+ 2
+ );
+ expect( screen.getAllByText( /Custom billing error/i ) ).toHaveLength(
+ 2
+ );
+ // Clean up notices.
+ await act( () =>
+ dispatch( noticesStore ).removeNotice(
+ 'custom-subcontext-test-error',
+ 'wc/checkout/shipping-address'
+ )
+ );
+ await act( () =>
+ dispatch( noticesStore ).removeNotice(
+ 'custom-subcontext-test-error',
+ 'wc/checkout/billing-address'
+ )
+ );
+ } );
+
+ it( 'Combine same notices from several contexts', async () => {
+ dispatch( noticesStore ).createErrorNotice( 'Custom generic error', {
+ id: 'custom-subcontext-test-error',
+ context: 'wc/checkout/shipping-address',
+ } );
+ dispatch( noticesStore ).createErrorNotice( 'Custom generic error', {
+ id: 'custom-subcontext-test-error',
+ context: 'wc/checkout/billing-address',
+ } );
+ render(
+
+ );
+ // This should match against 2 elements; A written and spoken message.
+ expect( screen.getAllByText( /Custom generic error/i ) ).toHaveLength(
+ 2
+ );
+ // Clean up notices.
+ await act( () =>
+ dispatch( noticesStore ).removeNotice(
+ 'custom-subcontext-test-error',
+ 'wc/checkout/shipping-address'
+ )
+ );
+ await act( () =>
+ dispatch( noticesStore ).removeNotice(
+ 'custom-subcontext-test-error',
+ 'wc/checkout/billing-address'
+ )
+ );
+ } );
} );
diff --git a/packages/checkout/components/store-notices-container/types.ts b/packages/checkout/components/store-notices-container/types.ts
index 332ea079f5d..63e1db419b7 100644
--- a/packages/checkout/components/store-notices-container/types.ts
+++ b/packages/checkout/components/store-notices-container/types.ts
@@ -8,7 +8,7 @@ import type {
export interface StoreNoticesContainerProps {
className?: string | undefined;
- context: string;
+ context: string | string[];
// List of additional notices that were added inline and not stored in the `core/notices` store.
additionalNotices?: ( NoticeType & NoticeOptions )[];
}