Commit cfa1c0a76d5 for woocommerce
commit cfa1c0a76d5c310f1c974100a13e1f5bd6e7bd7a
Author: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu Jun 4 16:16:02 2026 -0500
Blocks: Fix JS crash in useCollection when store error is not an Error instance (#64218)
* Initial plan
* Fix useCollection to handle non-Error objects from store gracefully
Agent-Logs-Url: https://github.com/woocommerce/woocommerce/sessions/b5891181-0739-4b34-89e4-08d3ccdbcd7f
Co-authored-by: kraftbj <88897+kraftbj@users.noreply.github.com>
* Fix fallback message handling in useCollection
* fix: preserve error metadata when wrapping non-Error store errors
* refactor: extract wrapNonError helper and improve fallback context
* fix: deduplicate non-Error log across useSelect double-invocation
* refactor: simplify non-Error handling to inline message-or-fallback
Per review feedback: store errors normally carry a message, so drop the
wrapNonError helper, metadata preservation, and dedup. Inline the non-Error
branch to throw a real Error with the original message or a generic fallback.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kraftbj <88897+kraftbj@users.noreply.github.com>
Co-authored-by: Brandon Kraft <public@brandonkraft.com>
diff --git a/plugins/woocommerce/changelog/fix-blocks-use-collection-non-error-handling b/plugins/woocommerce/changelog/fix-blocks-use-collection-non-error-handling
new file mode 100644
index 00000000000..7ed2ff9c4d6
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-blocks-use-collection-non-error-handling
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Blocks: Fix JS crash when the store returns a non-Error value in useCollection; convert it to a proper Error instance so the error boundary handles it gracefully.
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/collections/test/use-collection.jsx b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/collections/test/use-collection.jsx
index 3bc3bd7ab97..f325d91df77 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/collections/test/use-collection.jsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/collections/test/use-collection.jsx
@@ -328,4 +328,56 @@ describe( 'useCollection', () => {
[ 10, 30 ],
] );
} );
+ const renderWithStoreError = ( errorValue ) => {
+ mocks.selectors.getCollectionError.mockReturnValue( errorValue );
+ const TestComponent = getTestComponent();
+ act( () => {
+ renderer = TestRenderer.create(
+ getWrappedComponents( TestComponent, {
+ options: {
+ namespace: 'test/store',
+ resourceName: 'products',
+ query: { bar: 'foo' },
+ },
+ } )
+ );
+ } );
+ //eslint-disable-next-line testing-library/await-async-query
+ return renderer.root.findByType( 'div' ).props;
+ };
+
+ it( 'should propagate an Error instance from the store via the error boundary', () => {
+ const error = new Error( 'A real error' );
+ const props = renderWithStoreError( error );
+ expect( props[ 'data-error' ] ).toBeInstanceOf( Error );
+ expect( props[ 'data-error' ].message ).toBe( 'A real error' );
+ expect( console ).toHaveErrored( /your React components:/ );
+ renderer.unmount();
+ } );
+ it( 'should convert a non-Error object with a message into an Error instance', () => {
+ const error = { code: 'rest_no_route', message: 'No route found.' };
+ const props = renderWithStoreError( error );
+ expect( props[ 'data-error' ] ).toBeInstanceOf( Error );
+ expect( props[ 'data-error' ].message ).toBe( 'No route found.' );
+ expect( console ).toHaveErrored( /your React components:/ );
+ renderer.unmount();
+ } );
+ it( 'should use a fallback message when a non-Error object has no message', () => {
+ const props = renderWithStoreError( { code: 500 } );
+ expect( props[ 'data-error' ] ).toBeInstanceOf( Error );
+ expect( props[ 'data-error' ].message ).toBe(
+ 'Something went wrong while loading data.'
+ );
+ expect( console ).toHaveErrored( /your React components:/ );
+ renderer.unmount();
+ } );
+ it( 'should use a fallback message when a primitive value is returned from the store', () => {
+ const props = renderWithStoreError( 'oops' );
+ expect( props[ 'data-error' ] ).toBeInstanceOf( Error );
+ expect( props[ 'data-error' ].message ).toBe(
+ 'Something went wrong while loading data.'
+ );
+ expect( console ).toHaveErrored( /your React components:/ );
+ renderer.unmount();
+ } );
} );
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/collections/use-collection.ts b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/collections/use-collection.ts
index 161a465c741..d2a781e6e8d 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/collections/use-collection.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/collections/use-collection.ts
@@ -6,6 +6,7 @@ import { useSelect } from '@wordpress/data';
import { useRef } from '@wordpress/element';
import { useShallowEqual, useThrowError } from '@woocommerce/base-hooks';
import { isError } from '@woocommerce/types';
+import { __ } from '@wordpress/i18n';
/**
* This is a custom hook that is wired up to the `wc/store/collections` data
@@ -95,9 +96,20 @@ export const useCollection = < T >(
if ( isError( error ) ) {
throwError( error );
} else {
- throw new Error(
- 'TypeError: `error` object is not an instance of Error constructor'
- );
+ // Store errors (e.g. from the Store API) normally carry a
+ // message, but guard against non-Error values that don't so
+ // the error boundary always receives a real Error instance.
+ const message =
+ typeof error === 'object' &&
+ error !== null &&
+ typeof ( error as { message?: unknown } ).message ===
+ 'string'
+ ? ( error as { message: string } ).message
+ : __(
+ 'Something went wrong while loading data.',
+ 'woocommerce'
+ );
+ throwError( new Error( message ) );
}
}