Commit aa9d5439099 for woocommerce
commit aa9d5439099de069af194a109c66641177455be0
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Fri Oct 30 17:17:56 2026 +0100
Product Collection block: don't save columns and shrinkColumns attributes when not used (#70289)
* Product Collection block: don't save columns and shrinkColumns attributes when not used
* Add changelog
* Linting
* Simplify if clause
* Add guard against missing columns
* Linting
* Simplify test assertions
diff --git a/plugins/woocommerce/changelog/fix-product-collection-dont-save-columns-shrink-columns-unnecessarily b/plugins/woocommerce/changelog/fix-product-collection-dont-save-columns-shrink-columns-unnecessarily
new file mode 100644
index 00000000000..9f4518d1330
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-product-collection-dont-save-columns-shrink-columns-unnecessarily
@@ -0,0 +1,4 @@
+Significance: patch
+Type: tweak
+
+Product Collection block: don't save columns and shrinkColumns attributes when not used
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/columns-control.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/columns-control.tsx
index bf18863f713..99f57d79c83 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/columns-control.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/columns-control.tsx
@@ -12,8 +12,8 @@ import {
/**
* Internal dependencies
*/
-import { DisplayLayoutControlProps } from '../../types';
-import { getDefaultDisplayLayout } from '../../utils';
+import { DisplayLayoutControlProps, LayoutOptions } from '../../types';
+import { getDefaultDisplayLayout, sanitizeDisplayLayout } from '../../utils';
const columnsLabel = __( 'Columns', 'woocommerce' );
const toggleLabel = __( 'Responsive', 'woocommerce' );
@@ -30,22 +30,23 @@ interface ColumnsControlProps extends DisplayLayoutControlProps {
const ColumnsControl = ( props: ColumnsControlProps ) => {
const { maxColumns, hideResponsiveToggle } = props;
const { type, columns, shrinkColumns } = props.displayLayout;
- const showColumnsControl = type === 'flex';
+ const showColumnsControl = type === LayoutOptions.GRID;
const defaultLayout = getDefaultDisplayLayout();
+ const columnCount = columns ?? defaultLayout.columns;
const onShrinkColumnsToggleChange = ( value: boolean ) => {
props.setAttributes( {
- displayLayout: {
+ displayLayout: sanitizeDisplayLayout( {
...props.displayLayout,
shrinkColumns: value,
- },
+ } ),
} );
};
const onPanelDeselect = () => {
props.setAttributes( {
- displayLayout: defaultLayout,
+ displayLayout: sanitizeDisplayLayout( defaultLayout ),
} );
};
@@ -54,10 +55,10 @@ const ColumnsControl = ( props: ColumnsControlProps ) => {
return;
}
props.setAttributes( {
- displayLayout: {
+ displayLayout: sanitizeDisplayLayout( {
...props.displayLayout,
columns: value,
- },
+ } ),
} );
};
@@ -68,7 +69,7 @@ const ColumnsControl = ( props: ColumnsControlProps ) => {
<>
<ToolsPanelItem
label={ columnsLabel }
- hasValue={ () => defaultLayout?.columns !== columns }
+ hasValue={ () => defaultLayout.columns !== columnCount }
isShownByDefault
onDeselect={ onPanelDeselect }
>
@@ -77,9 +78,9 @@ const ColumnsControl = ( props: ColumnsControlProps ) => {
__nextHasNoMarginBottom
label={ columnsLabel }
onChange={ onColumnsChange }
- value={ columns }
+ value={ columnCount }
min={ 1 }
- max={ Math.max( effectiveMaxColumns, columns ) }
+ max={ Math.max( effectiveMaxColumns, columnCount ) }
/>
</ToolsPanelItem>
{ ! hideResponsiveToggle && (
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/layout-options-control.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/layout-options-control.tsx
index 4b63f3f3a8b..5e5805367e7 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/layout-options-control.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/layout-options-control.tsx
@@ -19,6 +19,7 @@ import {
* Internal dependencies
*/
import { DisplayLayoutControlProps, LayoutOptions } from '../../types';
+import { getDefaultDisplayLayout, sanitizeDisplayLayout } from '../../utils';
const getHelpText = ( layoutOptions: LayoutOptions ) => {
switch ( layoutOptions ) {
@@ -42,14 +43,16 @@ const getHelpText = ( layoutOptions: LayoutOptions ) => {
const DEFAULT_VALUE = LayoutOptions.GRID;
const LayoutOptionsControl = ( props: DisplayLayoutControlProps ) => {
- const { type, columns, shrinkColumns } = props.displayLayout;
- const setDisplayLayout = ( displayLayout: LayoutOptions ) => {
+ const { type } = props.displayLayout;
+ const setDisplayLayout = ( nextType: LayoutOptions ) => {
props.setAttributes( {
- displayLayout: {
- type: displayLayout,
- columns,
- shrinkColumns,
- },
+ displayLayout: sanitizeDisplayLayout( {
+ ...( nextType === LayoutOptions.GRID
+ ? getDefaultDisplayLayout()
+ : {} ),
+ ...props.displayLayout,
+ type: nextType,
+ } ),
} );
};
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/use-email-column-adjustments.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/use-email-column-adjustments.ts
index 9f26638178b..7d11a5fa7cd 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/use-email-column-adjustments.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/use-email-column-adjustments.ts
@@ -7,7 +7,8 @@ import { useEffect } from '@wordpress/element';
/**
* Internal dependencies
*/
-import { type ProductCollectionAttributes } from '../../types';
+import { type ProductCollectionAttributes, LayoutOptions } from '../../types';
+import { sanitizeDisplayLayout } from '../../utils';
const MAX_EMAIL_COLUMNS = 2;
@@ -32,16 +33,18 @@ const useEmailColumnAdjustments = (
return;
}
- // Only adjust columns if currently more than the max allowed for email
+ // Only adjust columns if currently more than the max allowed for email.
+ // Other layouts do not store a column count.
if (
+ displayLayout?.type === LayoutOptions.GRID &&
displayLayout.columns &&
displayLayout.columns > MAX_EMAIL_COLUMNS
) {
setAttributes( {
- displayLayout: {
+ displayLayout: sanitizeDisplayLayout( {
...displayLayout,
columns: MAX_EMAIL_COLUMNS,
- },
+ } ),
} );
}
}, [ isEmail, displayLayout, setAttributes ] );
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/test/editor-contracts.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/test/editor-contracts.tsx
index 10d7b26d9f1..71b045cf2f9 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/test/editor-contracts.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/test/editor-contracts.tsx
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { act, render, screen, waitFor } from '@testing-library/react';
+import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
createBlock,
@@ -37,6 +37,7 @@ import {
import {
addProductCollectionToQueryPaginationParentOrAncestor,
getDefaultValueOfInherit,
+ sanitizeDisplayLayout,
} from '../utils';
import {
CoreCollectionNames,
@@ -183,6 +184,52 @@ describe( 'Product Collection editor contracts', () => {
}
);
+ it.each( [
+ {
+ layout: LayoutOptions.STACK,
+ saved: { type: LayoutOptions.STACK },
+ },
+ {
+ layout: LayoutOptions.CAROUSEL,
+ saved: { type: LayoutOptions.CAROUSEL },
+ },
+ ] )(
+ 'omits column settings from the saved $layout markup',
+ ( { layout, saved } ) => {
+ const block = createBlock( PRODUCT_COLLECTION_BLOCK_NAME, {
+ displayLayout: sanitizeDisplayLayout( {
+ type: layout,
+ columns: 4,
+ shrinkColumns: true,
+ } ),
+ } );
+ const serialized = serialize( block );
+
+ expect( serialized ).toContain(
+ `"displayLayout":${ JSON.stringify( saved ) }`
+ );
+ }
+ );
+
+ it( 'keeps column settings in the saved Grid markup', () => {
+ const block = createBlock( PRODUCT_COLLECTION_BLOCK_NAME, {
+ displayLayout: sanitizeDisplayLayout( {
+ type: LayoutOptions.GRID,
+ columns: 4,
+ shrinkColumns: false,
+ } ),
+ } );
+ const serialized = serialize( block );
+
+ expect( serialized ).toContain(
+ `"displayLayout":${ JSON.stringify( {
+ type: LayoutOptions.GRID,
+ columns: 4,
+ shrinkColumns: false,
+ } ) }`
+ );
+ } );
+
it( 'adds Product Collection to the real Pagination metadata filter', () => {
addProductCollectionToQueryPaginationParentOrAncestor();
@@ -391,14 +438,12 @@ describe( 'default catalog order control', () => {
trackInteraction={ trackInteraction }
/>
);
- await act( async () => {
- await user.selectOptions(
- screen.getByRole( 'combobox', {
- name: 'Default sort by',
- } ),
- 'price-desc'
- );
- } );
+ await user.selectOptions(
+ screen.getByRole( 'combobox', {
+ name: 'Default sort by',
+ } ),
+ 'price-desc'
+ );
expect( editEntityRecord ).toHaveBeenCalledWith(
'root',
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/types.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/types.ts
index d1cb54043f8..479fedf5048 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/types.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/types.ts
@@ -56,8 +56,14 @@ export enum WidthOptions {
export interface ProductCollectionDisplayLayout {
type: LayoutOptions;
- columns: number;
- shrinkColumns: boolean;
+ /**
+ * Column count for the Grid layout. Omitted for Stack and Carousel.
+ */
+ columns?: number;
+ /**
+ * Whether Grid columns shrink on smaller screens. Omitted for Stack and Carousel.
+ */
+ shrinkColumns?: boolean;
}
export interface ProductCollectionDimensions {
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx
index 65727800303..3402ff63c27 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx
@@ -35,6 +35,7 @@ import {
SetPreviewState,
ProductCollectionUIStatesInEditor,
CoreCollectionNames,
+ LayoutOptions,
} from './types';
import {
coreQueryPaginationBlockName,
@@ -496,8 +497,33 @@ export const getDefaultQueryForSettingsSection = (
pages: DEFAULT_QUERY.pages,
} );
-export const getDefaultDisplayLayout = () =>
- DEFAULT_ATTRIBUTES.displayLayout as ProductCollectionDisplayLayout;
+export const getDefaultDisplayLayout =
+ (): Required< ProductCollectionDisplayLayout > =>
+ DEFAULT_ATTRIBUTES.displayLayout as Required< ProductCollectionDisplayLayout >;
+
+/**
+ * Drop column settings the current layout does not use.
+ * Stack and Carousel ignore `columns` and `shrinkColumns`, so those keys are
+ * left out of the attributes that get saved in the block comment.
+ */
+export function sanitizeDisplayLayout(
+ displayLayout?: Partial< ProductCollectionDisplayLayout > | null
+): ProductCollectionDisplayLayout {
+ if (
+ displayLayout?.type === LayoutOptions.STACK ||
+ displayLayout?.type === LayoutOptions.CAROUSEL
+ ) {
+ return { type: displayLayout.type };
+ }
+
+ const defaults = getDefaultDisplayLayout();
+
+ return {
+ type: displayLayout?.type ?? defaults.type,
+ columns: displayLayout?.columns ?? defaults.columns,
+ shrinkColumns: displayLayout?.shrinkColumns ?? defaults.shrinkColumns,
+ };
+}
export const getDefaultSettings = (
currentAttributes: ProductCollectionAttributes
@@ -585,7 +611,7 @@ export const useGetProduct = ( productId: number | undefined ) => {
productId
) ) as ProductResponseItem;
setProduct( fetchedProduct );
- } catch ( error ) {
+ } catch {
setProduct( null );
} finally {
setIsLoading( false );
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-template/edit.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-template/edit.tsx
index 4b53a495a7e..a0329c03ca8 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-template/edit.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-template/edit.tsx
@@ -222,11 +222,11 @@ const ProductTemplateEdit = (
},
queryContext = [ { page: 1 } ],
templateSlug,
- displayLayout: { type: layoutType, columns, shrinkColumns } = {
- type: 'flex',
- columns: 3,
- shrinkColumns: false,
- },
+ displayLayout: {
+ type: layoutType = 'flex',
+ columns = 3,
+ shrinkColumns = false,
+ } = {},
queryContextIncludes = [],
__privateProductCollectionPreviewState,
},
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php
index 5ea89fb8483..57368f2a9f4 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductTemplate.php
@@ -61,13 +61,16 @@ class ProductTemplate extends AbstractBlock {
$classnames = '';
if ( isset( $block->context['displayLayout'] ) && isset( $block->context['query'] ) ) {
- $classnames = 'is-product-collection-layout-' . $block->context['displayLayout']['type'] . ' ';
+ $type = $block->context['displayLayout']['type'] ?? '';
+ $columns = $block->context['displayLayout']['columns'] ?? null;
- if ( isset( $block->context['displayLayout']['type'] ) && 'flex' === $block->context['displayLayout']['type'] ) {
+ $classnames = 'is-product-collection-layout-' . $type . ' ';
+
+ if ( 'flex' === $type && ! empty( $columns ) ) {
if ( isset( $block->context['displayLayout']['shrinkColumns'] ) && $block->context['displayLayout']['shrinkColumns'] ) {
- $classnames = "wc-block-product-template__responsive columns-{$block->context['displayLayout']['columns']}";
+ $classnames = "wc-block-product-template__responsive columns-{$columns}";
} else {
- $classnames = "is-flex-container columns-{$block->context['displayLayout']['columns']}";
+ $classnames = "is-flex-container columns-{$columns}";
}
}
}