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
This commit is contained in:
parent
8da8a70839
commit
b3e76c41b4
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: dev
|
||||||
|
|
||||||
|
Request attributes conditionally #47361
|
|
@ -22,5 +22,6 @@
|
||||||
"lock": false,
|
"lock": false,
|
||||||
"__experimentalToolbar": false
|
"__experimentalToolbar": false
|
||||||
},
|
},
|
||||||
|
"usesContext": [ "isInSelectedTab" ],
|
||||||
"editorStyle": "file:./editor.css"
|
"editorStyle": "file:./editor.css"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { BlockAttributes } from '@wordpress/blocks';
|
import { BlockAttributes } from '@wordpress/blocks';
|
||||||
import { createElement } from '@wordpress/element';
|
import { createElement, useEffect } from '@wordpress/element';
|
||||||
import { useWooBlockProps } from '@woocommerce/block-templates';
|
import { useWooBlockProps } from '@woocommerce/block-templates';
|
||||||
import { ProductProductAttribute } from '@woocommerce/data';
|
import { ProductProductAttribute } from '@woocommerce/data';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { recordEvent } from '@woocommerce/tracks';
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore No types for this exist yet.
|
// @ts-ignore No types for this exist yet.
|
||||||
// eslint-disable-next-line @woocommerce/dependency-group
|
// eslint-disable-next-line @woocommerce/dependency-group
|
||||||
|
@ -13,11 +15,13 @@ import { useEntityProp, useEntityId } from '@wordpress/core-data';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { Attributes as AttributesContainer } from '../../../components/attributes/attributes';
|
import { AttributeControl } from '../../../components/attribute-control';
|
||||||
import { ProductEditorBlockEditProps } from '../../../types';
|
import { ProductEditorBlockEditProps } from '../../../types';
|
||||||
|
import { useProductAttributes } from '../../../hooks/use-product-attributes';
|
||||||
|
|
||||||
export function AttributesBlockEdit( {
|
export function AttributesBlockEdit( {
|
||||||
attributes,
|
attributes,
|
||||||
|
context: { isInSelectedTab },
|
||||||
}: ProductEditorBlockEditProps< BlockAttributes > ) {
|
}: ProductEditorBlockEditProps< BlockAttributes > ) {
|
||||||
const [ entityAttributes, setEntityAttributes ] = useEntityProp<
|
const [ entityAttributes, setEntityAttributes ] = useEntityProp<
|
||||||
ProductProductAttribute[]
|
ProductProductAttribute[]
|
||||||
|
@ -27,12 +31,77 @@ export function AttributesBlockEdit( {
|
||||||
|
|
||||||
const blockProps = useWooBlockProps( attributes );
|
const blockProps = useWooBlockProps( attributes );
|
||||||
|
|
||||||
|
const {
|
||||||
|
attributes: attributeList,
|
||||||
|
fetchAttributes,
|
||||||
|
handleChange,
|
||||||
|
} = useProductAttributes( {
|
||||||
|
allAttributes: entityAttributes,
|
||||||
|
onChange: setEntityAttributes,
|
||||||
|
productId,
|
||||||
|
} );
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
if ( isInSelectedTab ) {
|
||||||
|
fetchAttributes();
|
||||||
|
}
|
||||||
|
}, [ entityAttributes, isInSelectedTab ] );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div { ...blockProps }>
|
<div { ...blockProps }>
|
||||||
<AttributesContainer
|
<AttributeControl
|
||||||
productId={ productId }
|
value={ attributeList }
|
||||||
value={ entityAttributes }
|
disabledAttributeIds={ entityAttributes
|
||||||
onChange={ setEntityAttributes }
|
.filter( ( attr ) => !! 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 }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,5 +22,6 @@
|
||||||
"lock": false,
|
"lock": false,
|
||||||
"__experimentalToolbar": false
|
"__experimentalToolbar": false
|
||||||
},
|
},
|
||||||
|
"usesContext": [ "postType", "isInSelectedTab" ],
|
||||||
"editorStyle": "file:./editor.css"
|
"editorStyle": "file:./editor.css"
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { Button } from '@wordpress/components';
|
||||||
import {
|
import {
|
||||||
createElement,
|
createElement,
|
||||||
createInterpolateElement,
|
createInterpolateElement,
|
||||||
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from '@wordpress/element';
|
} from '@wordpress/element';
|
||||||
import { useWooBlockProps } from '@woocommerce/block-templates';
|
import { useWooBlockProps } from '@woocommerce/block-templates';
|
||||||
|
@ -37,7 +38,7 @@ import { ProductTShirt } from './images';
|
||||||
|
|
||||||
export function Edit( {
|
export function Edit( {
|
||||||
attributes: blockAttributes,
|
attributes: blockAttributes,
|
||||||
context,
|
context: { postType, isInSelectedTab },
|
||||||
}: ProductEditorBlockEditProps< BlockAttributes > ) {
|
}: ProductEditorBlockEditProps< BlockAttributes > ) {
|
||||||
const blockProps = useWooBlockProps( blockAttributes );
|
const blockProps = useWooBlockProps( blockAttributes );
|
||||||
const { generateProductVariations } = useProductVariationsHelper();
|
const { generateProductVariations } = useProductVariationsHelper();
|
||||||
|
@ -57,19 +58,26 @@ export function Edit( {
|
||||||
'default_attributes'
|
'default_attributes'
|
||||||
);
|
);
|
||||||
|
|
||||||
const { postType } = context;
|
|
||||||
const productId = useEntityId( 'postType', postType );
|
const productId = useEntityId( 'postType', postType );
|
||||||
|
|
||||||
const { attributes, handleChange } = useProductAttributes( {
|
const { attributes, fetchAttributes, handleChange } = useProductAttributes(
|
||||||
|
{
|
||||||
allAttributes: entityAttributes,
|
allAttributes: entityAttributes,
|
||||||
isVariationAttributes: true,
|
isVariationAttributes: true,
|
||||||
productId: useEntityId( 'postType', 'product' ),
|
productId,
|
||||||
onChange( values, defaultAttributes ) {
|
onChange( values, defaultAttributes ) {
|
||||||
setEntityAttributes( values );
|
setEntityAttributes( values );
|
||||||
setEntityDefaultAttributes( defaultAttributes );
|
setEntityDefaultAttributes( defaultAttributes );
|
||||||
generateProductVariations( values, defaultAttributes );
|
generateProductVariations( values, defaultAttributes );
|
||||||
},
|
},
|
||||||
} );
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
if ( isInSelectedTab ) {
|
||||||
|
fetchAttributes();
|
||||||
|
}
|
||||||
|
}, [ isInSelectedTab, entityAttributes ] );
|
||||||
|
|
||||||
const localAttributeNames = attributes
|
const localAttributeNames = attributes
|
||||||
.filter( ( attr ) => attr.id === 0 )
|
.filter( ( attr ) => attr.id === 0 )
|
||||||
|
|
|
@ -10,28 +10,26 @@ import { recordEvent } from '@woocommerce/tracks';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { AttributeControl } from '../attribute-control';
|
import { AttributeControl } from '../attribute-control';
|
||||||
import { useProductAttributes } from '../../hooks/use-product-attributes';
|
|
||||||
|
|
||||||
type AttributesProps = {
|
type AttributesProps = {
|
||||||
|
attributeList?: ProductProductAttribute[];
|
||||||
value: ProductProductAttribute[];
|
value: ProductProductAttribute[];
|
||||||
onChange: ( value: ProductProductAttribute[] ) => void;
|
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 > = ( {
|
export const Attributes: React.FC< AttributesProps > = ( {
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
productId,
|
attributeList = [],
|
||||||
} ) => {
|
} ) => {
|
||||||
const { attributes, handleChange } = useProductAttributes( {
|
|
||||||
allAttributes: value,
|
|
||||||
onChange,
|
|
||||||
productId,
|
|
||||||
} );
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AttributeControl
|
<AttributeControl
|
||||||
value={ attributes }
|
value={ attributeList }
|
||||||
disabledAttributeIds={ value
|
disabledAttributeIds={ value
|
||||||
.filter( ( attr ) => !! attr.variation )
|
.filter( ( attr ) => !! attr.variation )
|
||||||
.map( ( attr ) => attr.id ) }
|
.map( ( attr ) => attr.id ) }
|
||||||
|
@ -44,14 +42,14 @@ export const Attributes: React.FC< AttributesProps > = ( {
|
||||||
onAdd={ () => {
|
onAdd={ () => {
|
||||||
recordEvent( 'product_add_attributes_modal_add_button_click' );
|
recordEvent( 'product_add_attributes_modal_add_button_click' );
|
||||||
} }
|
} }
|
||||||
onChange={ handleChange }
|
onChange={ onChange }
|
||||||
onNewModalCancel={ () => {
|
onNewModalCancel={ () => {
|
||||||
recordEvent(
|
recordEvent(
|
||||||
'product_add_attributes_modal_cancel_button_click'
|
'product_add_attributes_modal_cancel_button_click'
|
||||||
);
|
);
|
||||||
} }
|
} }
|
||||||
onNewModalOpen={ () => {
|
onNewModalOpen={ () => {
|
||||||
if ( ! attributes.length ) {
|
if ( ! attributeList.length ) {
|
||||||
recordEvent( 'product_add_first_attribute_button_click' );
|
recordEvent( 'product_add_first_attribute_button_click' );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,6 +152,7 @@ describe( 'useProductAttributes', () => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
result.current.fetchAttributes();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
expect( resolveSelect ).not.toHaveBeenCalled();
|
expect( resolveSelect ).not.toHaveBeenCalled();
|
||||||
expect( result.current.attributes ).toEqual( [] );
|
expect( result.current.attributes ).toEqual( [] );
|
||||||
|
@ -175,6 +176,7 @@ describe( 'useProductAttributes', () => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
result.current.fetchAttributes();
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
result.current.handleChange( [
|
result.current.handleChange( [
|
||||||
|
@ -210,6 +212,7 @@ describe( 'useProductAttributes', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
|
result.current.fetchAttributes();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
result.current.handleChange( [
|
result.current.handleChange( [
|
||||||
{ ...testAttributes[ 0 ], isDefault: false },
|
{ ...testAttributes[ 0 ], isDefault: false },
|
||||||
|
@ -242,6 +245,7 @@ describe( 'useProductAttributes', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
|
result.current.fetchAttributes();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
result.current.handleChange( [
|
result.current.handleChange( [
|
||||||
{ ...testAttributes[ 0 ], isDefault: false },
|
{ ...testAttributes[ 0 ], isDefault: false },
|
||||||
|
@ -274,6 +278,7 @@ describe( 'useProductAttributes', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
|
result.current.fetchAttributes();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
result.current.handleChange( [
|
result.current.handleChange( [
|
||||||
{ ...testAttributes[ 1 ], isDefault: false },
|
{ ...testAttributes[ 1 ], isDefault: false },
|
||||||
|
@ -305,6 +310,7 @@ describe( 'useProductAttributes', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
|
result.current.fetchAttributes();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
result.current.handleChange( [
|
result.current.handleChange( [
|
||||||
{ ...testAttributes[ 0 ], isDefault: false },
|
{ ...testAttributes[ 0 ], isDefault: false },
|
||||||
|
@ -336,6 +342,7 @@ describe( 'useProductAttributes', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
|
result.current.fetchAttributes();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
result.current.handleChange( [ { ...testAttributes[ 0 ] } ] );
|
result.current.handleChange( [ { ...testAttributes[ 0 ] } ] );
|
||||||
expect( onChange ).toHaveBeenCalledWith(
|
expect( onChange ).toHaveBeenCalledWith(
|
||||||
|
@ -371,6 +378,7 @@ describe( 'useProductAttributes', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
|
result.current.fetchAttributes();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
result.current.handleChange( [
|
result.current.handleChange( [
|
||||||
{ ...testAttributes[ 0 ], isDefault: true },
|
{ ...testAttributes[ 0 ], isDefault: true },
|
||||||
|
@ -415,6 +423,7 @@ describe( 'useProductAttributes', () => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
result.current.fetchAttributes();
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
expect( result.current.attributes.length ).toBe( 2 );
|
expect( result.current.attributes.length ).toBe( 2 );
|
||||||
|
@ -445,6 +454,7 @@ describe( 'useProductAttributes', () => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
result.current.fetchAttributes();
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
expect( result.current.attributes.length ).toBe( 2 );
|
expect( result.current.attributes.length ).toBe( 2 );
|
||||||
|
@ -460,6 +470,7 @@ describe( 'useProductAttributes', () => {
|
||||||
isVariationAttributes: false,
|
isVariationAttributes: false,
|
||||||
productId: 123,
|
productId: 123,
|
||||||
} );
|
} );
|
||||||
|
result.current.fetchAttributes();
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
expect( result.current.attributes.length ).toBe( 1 );
|
expect( result.current.attributes.length ).toBe( 1 );
|
||||||
|
@ -486,6 +497,7 @@ describe( 'useProductAttributes', () => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
result.current.fetchAttributes();
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
expect( result.current.attributes.length ).toBe( 3 );
|
expect( result.current.attributes.length ).toBe( 3 );
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
ProductDefaultAttribute,
|
ProductDefaultAttribute,
|
||||||
} from '@woocommerce/data';
|
} from '@woocommerce/data';
|
||||||
import { resolveSelect } from '@wordpress/data';
|
import { resolveSelect } from '@wordpress/data';
|
||||||
import { useCallback, useEffect, useState } from '@wordpress/element';
|
import { useCallback, useState } from '@wordpress/element';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -167,7 +167,7 @@ export function useProductAttributes( {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect( () => {
|
const fetchAttributes = useCallback( () => {
|
||||||
const [
|
const [
|
||||||
localAttributes,
|
localAttributes,
|
||||||
globalAttributes,
|
globalAttributes,
|
||||||
|
@ -190,6 +190,7 @@ export function useProductAttributes( {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
attributes,
|
attributes,
|
||||||
|
fetchAttributes,
|
||||||
handleChange,
|
handleChange,
|
||||||
setAttributes,
|
setAttributes,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue