Commit 12e32f9b560 for woocommerce

commit 12e32f9b5602b9802a8b9797479340592fd84674
Author: Marcus Karlos <manager1@onmail.com>
Date:   Tue Mar 3 17:22:09 2026 +0200

    Fix(store-notices): Add try/catch guards to prevent errors on empty context (#63402)

    * fix(store-notices): #63382 add try/catch context guards to prevent runtime errors

    Wraps `getContext` calls in try/catch blocks to handle scenarios where the Interactivity API context stack is empty (e.g., custom AJAX navigation). This prevents "Cannot read properties of undefined" errors and allows the store to degrade gracefully. Updated all getters and actions to safely handle null context returns.

    * Fix: Improve type safety and optional chaining in store-notices

    * Fix: Type safety and icon injection logic

    * Fix: Restore original indentation (tabs)

    * revert store-notices.ts to original

    Restore original indentation to limit diff to actual changes only

    Previous commits unintentionally changed the file indentation,
    causing the diff to show the entire file as modified.
    Restoring the original file before applying targeted fixes.

    * fix(store-notices): add try/catch guards and optional chaining to prevent runtime errors on empty context

    getStoreNoticeContext and getProductCollectionContext now return null
    instead of throwing when context stack is empty. All consumers updated
    to use optional chaining (?.) accordingly.

    Fix: Indentation and revert formatting changes
    Fixes woocommerce#63382

    * fix: Added try/catch guards to store notices to prevent fatal errors when context is missing.

    Fixed — only these exact 3 blocks have the wrong indentation (spaces/mixed instead of pure tabs).

    * Add changelog for store-notices try/catch fix

    * Delete .changeset directory

    * Add changelog entry

    * Update and rename fix-store-notices-context.md to fix-store-notices-context

    Removed wrong changelog file extension .md

    ---------

    Co-authored-by: Karol Manijak <20098064+kmanijak@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/fix-store-notices-context b/plugins/woocommerce/changelog/fix-store-notices-context
new file mode 100644
index 00000000000..4bc969f058a
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-store-notices-context
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Added try/catch guards to store notices to prevent fatal errors when context is missing.
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/stores/store-notices.ts b/plugins/woocommerce/client/blocks/assets/js/base/stores/store-notices.ts
index 7b40754e31c..68a484baa95 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/stores/store-notices.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/base/stores/store-notices.ts
@@ -17,17 +17,33 @@ type NoticeWithId = Notice & {
 	id: string;
 };

-const getStoreNoticeContext = getContextFn< {
+const getStoreNoticeContext = (): {
 	notices: NoticeWithId[];
 	notice: NoticeWithId;
-} >;
+} | null => {
+	try {
+		return getContextFn< {
+			notices: NoticeWithId[];
+			notice: NoticeWithId;
+		} >();
+	} catch ( e ) {
+		return null;
+	}
+};

 // Todo: Go back to the Store Notices block context once more than one context
 // can be added to an element (https://github.com/WordPress/gutenberg/discussions/62720).
-const getProductCollectionContext = () =>
-	getContextFn< {
-		notices: NoticeWithId[];
-	} >( 'woocommerce/product-collection' );
+const getProductCollectionContext = (): {
+	notices: NoticeWithId[];
+} | null => {
+	try {
+		return getContextFn< {
+			notices: NoticeWithId[];
+		} >( 'woocommerce/product-collection' );
+	} catch ( e ) {
+		return null;
+	}
+};

 type StoreNoticesState = {
 	get role(): string;
@@ -71,8 +87,8 @@ const { state } = store< Store >(
 			get role() {
 				const context = getStoreNoticeContext();
 				if (
-					context.notice.type === 'error' ||
-					context.notice.type === 'success'
+					context?.notice?.type === 'error' ||
+					context?.notice?.type === 'success'
 				) {
 					return 'alert';
 				}
@@ -80,26 +96,26 @@ const { state } = store< Store >(
 				return 'status';
 			},
 			get isError() {
-				const { notice } = getStoreNoticeContext();
-				return notice.type === 'error';
+				const context = getStoreNoticeContext();
+				return context?.notice?.type === 'error';
 			},
 			get isSuccess() {
-				const { notice } = getStoreNoticeContext();
-				return notice.type === 'success';
+				const context = getStoreNoticeContext();
+				return context?.notice?.type === 'success';
 			},
 			get isInfo() {
-				const { notice } = getStoreNoticeContext();
-				return notice.type === 'notice';
+				const context = getStoreNoticeContext();
+				return context?.notice?.type === 'notice';
 			},
 			get notices() {
 				const productCollectionContext = getProductCollectionContext();
-				if ( productCollectionContext ) {
-					return productCollectionContext?.notices;
+				if ( productCollectionContext?.notices ) {
+					return productCollectionContext.notices;
 				}

 				const context = getStoreNoticeContext();

-				if ( context && context.notices ) {
+				if ( context?.notices ) {
 					return context.notices;
 				}

@@ -131,12 +147,16 @@ const { state } = store< Store >(
 			removeNotice: ( noticeId: string | PointerEvent ) => {
 				const { notices } = state;

-				noticeId =
+				const resolvedId =
 					typeof noticeId === 'string'
 						? noticeId
-						: getStoreNoticeContext().notice.id;
+						: getStoreNoticeContext()?.notice?.id;
+
+				// If noticeId is not found (e.g., context was null), do nothing.
+				if ( ! resolvedId ) return;
+
 				const index = notices.findIndex(
-					( { id } ) => id === noticeId
+					( { id } ) => id === resolvedId
 				);
 				if ( index !== -1 ) {
 					notices.splice( index, 1 );
@@ -148,7 +168,8 @@ const { state } = store< Store >(
 				const context = getStoreNoticeContext();
 				const { ref } = getElement();

-				if ( ref ) {
+				if ( ref && context?.notice ) {
+					// Note: Notice content is sanitized server-side via wp_kses.
 					ref.innerHTML = context.notice.notice;
 				}
 			},
@@ -162,8 +183,11 @@ const { state } = store< Store >(
 			},

 			injectIcon: () => {
+				const context = getStoreNoticeContext();
 				const { ref } = getElement();
-				if ( ! ref ) {
+
+				// Guard against missing context or notice to prevent wrong icon injection.
+				if ( ! ref || ! context?.notice ) {
 					return;
 				}