From b3e76c41b4fa7bec4bdabc16163a21bd59ac351f Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Mon, 13 May 2024 14:00:31 -0300 Subject: [PATCH] Request attributes conditionally (#47361) * Request terms conditionally * Add changelog * Add used context * Transform fetchAttributes into a hook * Call fetchAttributes inside Attributes component * Fix tests * Remove useless useEntityId and fix dependency * Refactor attriburtes component * Add AttributeControl to Attributes component * Add warning comment to Attributes component --- .../dev-47357_request_terms_conditionally | 4 + .../product-fields/attributes/block.json | 1 + .../blocks/product-fields/attributes/edit.tsx | 81 +++++++++++++++++-- .../variation-options/block.json | 1 + .../product-fields/variation-options/edit.tsx | 32 +++++--- .../src/components/attributes/attributes.tsx | 22 +++-- .../hooks/test/use-product-attributes.test.ts | 12 +++ .../src/hooks/use-product-attributes.ts | 5 +- 8 files changed, 126 insertions(+), 32 deletions(-) create mode 100644 packages/js/product-editor/changelog/dev-47357_request_terms_conditionally diff --git a/packages/js/product-editor/changelog/dev-47357_request_terms_conditionally b/packages/js/product-editor/changelog/dev-47357_request_terms_conditionally new file mode 100644 index 00000000000..9dd17454990 --- /dev/null +++ b/packages/js/product-editor/changelog/dev-47357_request_terms_conditionally @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Request attributes conditionally #47361 diff --git a/packages/js/product-editor/src/blocks/product-fields/attributes/block.json b/packages/js/product-editor/src/blocks/product-fields/attributes/block.json index 9e4ebdf00a1..aed5dd2c121 100644 --- a/packages/js/product-editor/src/blocks/product-fields/attributes/block.json +++ b/packages/js/product-editor/src/blocks/product-fields/attributes/block.json @@ -22,5 +22,6 @@ "lock": false, "__experimentalToolbar": false }, + "usesContext": [ "isInSelectedTab" ], "editorStyle": "file:./editor.css" } diff --git a/packages/js/product-editor/src/blocks/product-fields/attributes/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/attributes/edit.tsx index d3cc42cc3ed..50ae61bde27 100644 --- a/packages/js/product-editor/src/blocks/product-fields/attributes/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/attributes/edit.tsx @@ -2,9 +2,11 @@ * External dependencies */ import { BlockAttributes } from '@wordpress/blocks'; -import { createElement } from '@wordpress/element'; +import { createElement, useEffect } from '@wordpress/element'; import { useWooBlockProps } from '@woocommerce/block-templates'; import { ProductProductAttribute } from '@woocommerce/data'; +import { __ } from '@wordpress/i18n'; +import { recordEvent } from '@woocommerce/tracks'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore No types for this exist yet. // eslint-disable-next-line @woocommerce/dependency-group @@ -13,11 +15,13 @@ import { useEntityProp, useEntityId } from '@wordpress/core-data'; /** * Internal dependencies */ -import { Attributes as AttributesContainer } from '../../../components/attributes/attributes'; +import { AttributeControl } from '../../../components/attribute-control'; import { ProductEditorBlockEditProps } from '../../../types'; +import { useProductAttributes } from '../../../hooks/use-product-attributes'; export function AttributesBlockEdit( { attributes, + context: { isInSelectedTab }, }: ProductEditorBlockEditProps< BlockAttributes > ) { const [ entityAttributes, setEntityAttributes ] = useEntityProp< ProductProductAttribute[] @@ -27,12 +31,77 @@ export function AttributesBlockEdit( { const blockProps = useWooBlockProps( attributes ); + const { + attributes: attributeList, + fetchAttributes, + handleChange, + } = useProductAttributes( { + allAttributes: entityAttributes, + onChange: setEntityAttributes, + productId, + } ); + + useEffect( () => { + if ( isInSelectedTab ) { + fetchAttributes(); + } + }, [ entityAttributes, isInSelectedTab ] ); + return (
- !! attr.variation ) + .map( ( attr ) => attr.id ) } + uiStrings={ { + disabledAttributeMessage: __( + 'Already used in Variations', + 'woocommerce' + ), + } } + onAdd={ () => { + recordEvent( + 'product_add_attributes_modal_add_button_click' + ); + } } + onChange={ handleChange } + onNewModalCancel={ () => { + recordEvent( + 'product_add_attributes_modal_cancel_button_click' + ); + } } + onNewModalOpen={ () => { + if ( ! attributeList.length ) { + recordEvent( + 'product_add_first_attribute_button_click' + ); + return; + } + recordEvent( 'product_add_attribute_button' ); + } } + onAddAnother={ () => { + recordEvent( + 'product_add_attributes_modal_add_another_attribute_button_click' + ); + } } + onRemoveItem={ () => { + recordEvent( + 'product_add_attributes_modal_remove_attribute_button_click' + ); + } } + onRemove={ () => + recordEvent( + 'product_remove_attribute_confirmation_confirm_click' + ) + } + onRemoveCancel={ () => + recordEvent( + 'product_remove_attribute_confirmation_cancel_click' + ) + } + termsAutoSelection="first" + defaultVisibility={ true } />
); diff --git a/packages/js/product-editor/src/blocks/product-fields/variation-options/block.json b/packages/js/product-editor/src/blocks/product-fields/variation-options/block.json index c7aa348db3d..4b2f7c70d33 100644 --- a/packages/js/product-editor/src/blocks/product-fields/variation-options/block.json +++ b/packages/js/product-editor/src/blocks/product-fields/variation-options/block.json @@ -22,5 +22,6 @@ "lock": false, "__experimentalToolbar": false }, + "usesContext": [ "postType", "isInSelectedTab" ], "editorStyle": "file:./editor.css" } diff --git a/packages/js/product-editor/src/blocks/product-fields/variation-options/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/variation-options/edit.tsx index e276ac22c39..f13b70cd0c1 100644 --- a/packages/js/product-editor/src/blocks/product-fields/variation-options/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/variation-options/edit.tsx @@ -7,6 +7,7 @@ import { Button } from '@wordpress/components'; import { createElement, createInterpolateElement, + useEffect, useMemo, } from '@wordpress/element'; import { useWooBlockProps } from '@woocommerce/block-templates'; @@ -37,7 +38,7 @@ import { ProductTShirt } from './images'; export function Edit( { attributes: blockAttributes, - context, + context: { postType, isInSelectedTab }, }: ProductEditorBlockEditProps< BlockAttributes > ) { const blockProps = useWooBlockProps( blockAttributes ); const { generateProductVariations } = useProductVariationsHelper(); @@ -57,19 +58,26 @@ export function Edit( { 'default_attributes' ); - const { postType } = context; const productId = useEntityId( 'postType', postType ); - const { attributes, handleChange } = useProductAttributes( { - allAttributes: entityAttributes, - isVariationAttributes: true, - productId: useEntityId( 'postType', 'product' ), - onChange( values, defaultAttributes ) { - setEntityAttributes( values ); - setEntityDefaultAttributes( defaultAttributes ); - generateProductVariations( values, defaultAttributes ); - }, - } ); + const { attributes, fetchAttributes, handleChange } = useProductAttributes( + { + allAttributes: entityAttributes, + isVariationAttributes: true, + productId, + onChange( values, defaultAttributes ) { + setEntityAttributes( values ); + setEntityDefaultAttributes( defaultAttributes ); + generateProductVariations( values, defaultAttributes ); + }, + } + ); + + useEffect( () => { + if ( isInSelectedTab ) { + fetchAttributes(); + } + }, [ isInSelectedTab, entityAttributes ] ); const localAttributeNames = attributes .filter( ( attr ) => attr.id === 0 ) diff --git a/packages/js/product-editor/src/components/attributes/attributes.tsx b/packages/js/product-editor/src/components/attributes/attributes.tsx index b9af12717d9..264d7256d08 100644 --- a/packages/js/product-editor/src/components/attributes/attributes.tsx +++ b/packages/js/product-editor/src/components/attributes/attributes.tsx @@ -10,28 +10,26 @@ import { recordEvent } from '@woocommerce/tracks'; * Internal dependencies */ import { AttributeControl } from '../attribute-control'; -import { useProductAttributes } from '../../hooks/use-product-attributes'; type AttributesProps = { + attributeList?: ProductProductAttribute[]; value: ProductProductAttribute[]; onChange: ( value: ProductProductAttribute[] ) => void; - productId?: number; }; +/** + * This component is no longer in active use. + * It is kept here for backward compatibility because is being used in the `AttributesField` component, under + * `plugins/woocommerce-admin/client/products/fills/attributes-section/attributes-field.tsx`. + */ export const Attributes: React.FC< AttributesProps > = ( { value, onChange, - productId, + attributeList = [], } ) => { - const { attributes, handleChange } = useProductAttributes( { - allAttributes: value, - onChange, - productId, - } ); - return ( !! attr.variation ) .map( ( attr ) => attr.id ) } @@ -44,14 +42,14 @@ export const Attributes: React.FC< AttributesProps > = ( { onAdd={ () => { recordEvent( 'product_add_attributes_modal_add_button_click' ); } } - onChange={ handleChange } + onChange={ onChange } onNewModalCancel={ () => { recordEvent( 'product_add_attributes_modal_cancel_button_click' ); } } onNewModalOpen={ () => { - if ( ! attributes.length ) { + if ( ! attributeList.length ) { recordEvent( 'product_add_first_attribute_button_click' ); return; } diff --git a/packages/js/product-editor/src/hooks/test/use-product-attributes.test.ts b/packages/js/product-editor/src/hooks/test/use-product-attributes.test.ts index 2ed6d4c8d73..4db09e1d783 100644 --- a/packages/js/product-editor/src/hooks/test/use-product-attributes.test.ts +++ b/packages/js/product-editor/src/hooks/test/use-product-attributes.test.ts @@ -152,6 +152,7 @@ describe( 'useProductAttributes', () => { }, } ); + result.current.fetchAttributes(); await waitForNextUpdate(); expect( resolveSelect ).not.toHaveBeenCalled(); expect( result.current.attributes ).toEqual( [] ); @@ -175,6 +176,7 @@ describe( 'useProductAttributes', () => { }, } ); + result.current.fetchAttributes(); jest.runOnlyPendingTimers(); await waitForNextUpdate(); result.current.handleChange( [ @@ -210,6 +212,7 @@ describe( 'useProductAttributes', () => { } ); jest.runOnlyPendingTimers(); + result.current.fetchAttributes(); await waitForNextUpdate(); result.current.handleChange( [ { ...testAttributes[ 0 ], isDefault: false }, @@ -242,6 +245,7 @@ describe( 'useProductAttributes', () => { } ); jest.runOnlyPendingTimers(); + result.current.fetchAttributes(); await waitForNextUpdate(); result.current.handleChange( [ { ...testAttributes[ 0 ], isDefault: false }, @@ -274,6 +278,7 @@ describe( 'useProductAttributes', () => { } ); jest.runOnlyPendingTimers(); + result.current.fetchAttributes(); await waitForNextUpdate(); result.current.handleChange( [ { ...testAttributes[ 1 ], isDefault: false }, @@ -305,6 +310,7 @@ describe( 'useProductAttributes', () => { } ); jest.runOnlyPendingTimers(); + result.current.fetchAttributes(); await waitForNextUpdate(); result.current.handleChange( [ { ...testAttributes[ 0 ], isDefault: false }, @@ -336,6 +342,7 @@ describe( 'useProductAttributes', () => { } ); jest.runOnlyPendingTimers(); + result.current.fetchAttributes(); await waitForNextUpdate(); result.current.handleChange( [ { ...testAttributes[ 0 ] } ] ); expect( onChange ).toHaveBeenCalledWith( @@ -371,6 +378,7 @@ describe( 'useProductAttributes', () => { } ); jest.runOnlyPendingTimers(); + result.current.fetchAttributes(); await waitForNextUpdate(); result.current.handleChange( [ { ...testAttributes[ 0 ], isDefault: true }, @@ -415,6 +423,7 @@ describe( 'useProductAttributes', () => { }, } ); + result.current.fetchAttributes(); jest.runOnlyPendingTimers(); await waitForNextUpdate(); expect( result.current.attributes.length ).toBe( 2 ); @@ -445,6 +454,7 @@ describe( 'useProductAttributes', () => { }, } ); + result.current.fetchAttributes(); jest.runOnlyPendingTimers(); await waitForNextUpdate(); expect( result.current.attributes.length ).toBe( 2 ); @@ -460,6 +470,7 @@ describe( 'useProductAttributes', () => { isVariationAttributes: false, productId: 123, } ); + result.current.fetchAttributes(); jest.runOnlyPendingTimers(); await waitForNextUpdate(); expect( result.current.attributes.length ).toBe( 1 ); @@ -486,6 +497,7 @@ describe( 'useProductAttributes', () => { }, } ); + result.current.fetchAttributes(); jest.runOnlyPendingTimers(); await waitForNextUpdate(); expect( result.current.attributes.length ).toBe( 3 ); diff --git a/packages/js/product-editor/src/hooks/use-product-attributes.ts b/packages/js/product-editor/src/hooks/use-product-attributes.ts index d5f9dc3c732..8fb176a1ccb 100644 --- a/packages/js/product-editor/src/hooks/use-product-attributes.ts +++ b/packages/js/product-editor/src/hooks/use-product-attributes.ts @@ -9,7 +9,7 @@ import { ProductDefaultAttribute, } from '@woocommerce/data'; import { resolveSelect } from '@wordpress/data'; -import { useCallback, useEffect, useState } from '@wordpress/element'; +import { useCallback, useState } from '@wordpress/element'; /** * Internal dependencies @@ -167,7 +167,7 @@ export function useProductAttributes( { } }; - useEffect( () => { + const fetchAttributes = useCallback( () => { const [ localAttributes, globalAttributes, @@ -190,6 +190,7 @@ export function useProductAttributes( { return { attributes, + fetchAttributes, handleChange, setAttributes, };