Variation switching when deleting (#40780)

* Add variation switcher hook for easier use across multiple components

* Add unregister function for validation

* Add changelogs

* Remove stray console

* Add unRegisterValidator function
This commit is contained in:
louwie17 2023-10-13 20:25:58 -03:00 committed by GitHub
parent b4474fc633
commit 7f25060044
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 178 additions and 60 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add unregister function to the validation provider and trigger this from the useValidation hook.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add useVariationSwitcher hook and make use of it in the VariationSwitcherFooter.

View File

@ -6,64 +6,46 @@ import { Button } from '@wordpress/components';
import { createElement } from '@wordpress/element';
import { arrowLeft, arrowRight, Icon } from '@wordpress/icons';
import { useSelect } from '@wordpress/data';
import { Product, ProductVariation } from '@woocommerce/data';
import { ProductVariation } from '@woocommerce/data';
import { recordEvent } from '@woocommerce/tracks';
import { getNewPath, navigateTo } from '@woocommerce/navigation';
/**
* Internal dependencies
*/
import { SwitcherLoadingPlaceholder } from './switcher-loading-placeholder';
import { VariationImagePlaceholder } from './variation-image-placeholder';
import { useVariationSwitcher } from '../../hooks/use-variation-switcher';
export type VariationSwitcherProps = {
productType?: string;
parentProductType?: string;
variationId: number;
parentId: number;
};
function getVariationName( variation: ProductVariation ): string {
return variation.attributes.map( ( attr ) => attr.option ).join( ', ' );
}
export function VariationSwitcherFooter( {
parentProductType,
variationId,
parentId,
}: VariationSwitcherProps ) {
const {
previousVariation,
nextVariation,
numberOfVariations,
...variationIndexes
} = useSelect(
( select ) => {
const { getEntityRecord } = select( 'core' );
const parentProduct = getEntityRecord< Product >(
'postType',
'product',
parentId
);
if ( parentProduct && parentProduct.variations ) {
const activeVariationIndex =
parentProduct.variations.indexOf( variationId );
const previousVariationIndex =
activeVariationIndex > 0
? activeVariationIndex - 1
: parentProduct.variations.length - 1;
const nextVariationIndex =
activeVariationIndex !== parentProduct.variations.length - 1
? activeVariationIndex + 1
: 0;
const previousVariationId =
parentProduct.variations[ previousVariationIndex ];
const nextVariationId =
parentProduct.variations[ nextVariationIndex ];
return {
nextVariationId,
previousVariationId,
activeVariationIndex,
nextVariationIndex,
previousVariationIndex,
numberOfVariations: parentProduct.variations.length,
goToNextVariation,
goToPreviousVariation,
} = useVariationSwitcher( {
variationId,
parentId,
parentProductType,
} );
const { previousVariation, nextVariation } = useSelect(
( select ) => {
const { getEntityRecord } = select( 'core' );
if ( numberOfVariations && numberOfVariations > 0 ) {
return {
previousVariation: getEntityRecord< ProductVariation >(
'postType',
'product_variation',
@ -78,36 +60,26 @@ export function VariationSwitcherFooter( {
}
return {};
},
[ variationId, parentId ]
[ nextVariationId, previousVariationId, numberOfVariations ]
);
function onPrevious() {
recordEvent( 'product_variation_switch_previous', {
variation_length: numberOfVariations,
variation_id: previousVariation?.id,
variation_index: variationIndexes.activeVariationIndex,
previous_variation_index: variationIndexes.previousVariationIndex,
} );
navigateTo( {
url: getNewPath(
{},
`/product/${ parentId }/variation/${ previousVariation?.id }`
),
variation_index: activeVariationIndex,
previous_variation_index: previousVariationIndex,
} );
goToPreviousVariation();
}
function onNext() {
recordEvent( 'product_variation_switch_next', {
variation_length: numberOfVariations,
variation_id: nextVariation?.id,
variation_index: variationIndexes.activeVariationIndex,
next_variation_index: variationIndexes.nextVariationIndex,
} );
navigateTo( {
url: getNewPath(
{},
`/product/${ parentId }/variation/${ nextVariation?.id }`
),
variation_index: activeVariationIndex,
next_variation_index: nextVariationIndex,
} );
goToNextVariation();
}
if ( ! numberOfVariations || numberOfVariations < 2 ) {
@ -132,7 +104,7 @@ export function VariationSwitcherFooter( {
) : (
<VariationImagePlaceholder className="woocommerce-product-variation-switcher-footer__product-image" />
) }
{ getVariationName( previousVariation ) }
{ previousVariation.name }
</Button>
) : (
<SwitcherLoadingPlaceholder position="left" />
@ -143,7 +115,7 @@ export function VariationSwitcherFooter( {
label={ __( 'Next', 'woocommerce' ) }
onClick={ onNext }
>
{ getVariationName( nextVariation ) }
{ nextVariation.name }
{ nextVariation.image ? (
<img
alt={ nextVariation.image.alt || '' }

View File

@ -11,6 +11,7 @@ export type ValidationContextProps< T > = {
validatorId: string,
validator: Validator< T >
): React.Ref< HTMLElement >;
unRegisterValidator( validatorId: string ): void;
validateField( name: string ): ValidatorResponse;
validateAll( newData?: Partial< T > ): Promise< ValidationErrors >;
};

View File

@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { useContext, useMemo, useState } from '@wordpress/element';
import { useContext, useMemo, useState, useEffect } from '@wordpress/element';
import { DependencyList } from 'react';
/**
@ -23,6 +23,12 @@ export function useValidation< T >(
[ validatorId, ...deps ]
);
useEffect( () => {
return () => {
context.unRegisterValidator( validatorId );
};
}, [] );
return {
ref,
error: context.errors[ validatorId ],

View File

@ -13,6 +13,7 @@ export const ValidationContext = createContext< ValidationContextProps< any > >(
{
errors: {},
registerValidator: () => () => {},
unRegisterValidator: () => () => {},
validateField: () => Promise.resolve( undefined ),
validateAll: () => Promise.resolve( {} ),
}

View File

@ -38,6 +38,15 @@ export function ValidationProvider< T >( {
};
}
function unRegisterValidator( validatorId: string ): void {
if ( validatorsRef.current[ validatorId ] ) {
delete validatorsRef.current[ validatorId ];
}
if ( fieldRefs.current[ validatorId ] ) {
delete fieldRefs.current[ validatorId ];
}
}
async function validateField(
validatorId: string,
newData?: Partial< T >
@ -89,6 +98,7 @@ export function ValidationProvider< T >( {
value={ {
errors,
registerValidator,
unRegisterValidator,
validateField,
validateAll,
} }

View File

@ -2,3 +2,4 @@ export { useProductHelper as __experimentalUseProductHelper } from './use-produc
export { useFeedbackBar as __experimentalUseFeedbackBar } from './use-feedback-bar';
export { useVariationsOrder as __experimentalUseVariationsOrder } from './use-variations-order';
export { useCurrencyInputProps as __experimentalUseCurrencyInputProps } from './use-currency-input-props';
export { useVariationSwitcher as __experimentalUseVariationSwitcher } from './use-variation-switcher';

View File

@ -0,0 +1,101 @@
/**
* External dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { Product } from '@woocommerce/data';
import { getNewPath, navigateTo } from '@woocommerce/navigation';
type VariationSwitcherProps = {
parentProductType?: string;
variationId?: number;
parentId?: number;
};
export function useVariationSwitcher( {
variationId,
parentId,
parentProductType,
}: VariationSwitcherProps ) {
const { invalidateResolution } = useDispatch( 'core' );
const variationValues = useSelect(
( select ) => {
if ( parentId === undefined ) {
return {};
}
const { getEntityRecord } = select( 'core' );
const parentProduct = getEntityRecord< Product >(
'postType',
parentProductType || 'product',
parentId
);
if (
variationId !== undefined &&
parentProduct &&
parentProduct.variations
) {
const activeVariationIndex =
parentProduct.variations.indexOf( variationId );
const previousVariationIndex =
activeVariationIndex > 0
? activeVariationIndex - 1
: parentProduct.variations.length - 1;
const nextVariationIndex =
activeVariationIndex !== parentProduct.variations.length - 1
? activeVariationIndex + 1
: 0;
return {
activeVariationIndex,
nextVariationIndex,
previousVariationIndex,
numberOfVariations: parentProduct.variations.length,
previousVariationId:
parentProduct.variations[ previousVariationIndex ],
nextVariationId:
parentProduct.variations[ nextVariationIndex ],
};
}
return {};
},
[ variationId, parentId ]
);
function invalidateVariationList() {
invalidateResolution( 'getEntityRecord', [
'postType',
parentProductType || 'product',
parentId,
] );
}
function goToNextVariation() {
if ( variationValues.nextVariationId === undefined ) {
return false;
}
navigateTo( {
url: getNewPath(
{},
`/product/${ parentId }/variation/${ variationValues.nextVariationId }`
),
} );
}
function goToPreviousVariation() {
if ( variationValues.previousVariationId === undefined ) {
return false;
}
navigateTo( {
url: getNewPath(
{},
`/product/${ parentId }/variation/${ variationValues.previousVariationId }`
),
} );
}
return {
...variationValues,
invalidateVariationList,
goToNextVariation,
goToPreviousVariation,
};
}

View File

@ -11,7 +11,10 @@ import {
ProductVariation,
} from '@woocommerce/data';
import { getNewPath, navigateTo } from '@woocommerce/navigation';
import { RemoveConfirmationModal } from '@woocommerce/product-editor';
import {
RemoveConfirmationModal,
__experimentalUseVariationSwitcher as useVariationSwitcher,
} from '@woocommerce/product-editor';
import { recordEvent } from '@woocommerce/tracks';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
@ -29,6 +32,12 @@ export const DeleteVariationMenuItem = ( {
const variationId = useEntityId( 'postType', 'product_variation' );
const { invalidateVariationList, goToNextVariation, numberOfVariations } =
useVariationSwitcher( {
parentId: productId ? parseInt( productId, 10 ) : undefined,
variationId,
} );
const [ name ] = useEntityProp< string >(
'postType',
'product_variation',
@ -82,9 +91,14 @@ export const DeleteVariationMenuItem = ( {
setShowModal( false );
onClose();
invalidateVariationList();
if ( numberOfVariations && numberOfVariations > 1 ) {
goToNextVariation();
} else {
navigateTo( {
url: getNewPath( {}, `/product/${ productId }` ),
} );
}
} )
.catch( () => {
createErrorNotice(

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Redirect to next variation if deleting a variation on the edit variation page.