From 0c5d01a6acb17d04a774fdf8346e40f4e1b2c2bd Mon Sep 17 00:00:00 2001 From: Tung Du Date: Fri, 22 Dec 2023 22:05:31 +0700 Subject: [PATCH] Interactive Price Filter: use `context` instead of `state` (#42980) * feat: use context instead of state * fix: temporary move the context to inner element for diffing to work * fix: update context before navigation for optimistic UI --- .../price-filter/components/price-slider.tsx | 4 +- .../inner-blocks/price-filter/frontend.ts | 151 +++++++++--------- .../inner-blocks/price-filter/types.ts | 30 +++- ...2976-price-filter-convert-state-to-context | 4 + .../BlockTypes/CollectionPriceFilter.php | 81 +++++----- 5 files changed, 140 insertions(+), 130 deletions(-) create mode 100644 plugins/woocommerce/changelog/42980-fix-42976-price-filter-convert-state-to-context diff --git a/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/components/price-slider.tsx b/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/components/price-slider.tsx index 0d4edfe7d4c..1d02b6c11aa 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/components/price-slider.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/components/price-slider.tsx @@ -52,7 +52,7 @@ export const PriceSlider = ( { attributes }: EditProps ) => { ); return ( - <> +
{ { priceMin } { priceMax }
- +
); }; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/frontend.ts b/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/frontend.ts index e8d52e9a3f9..5b7bf657d85 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/frontend.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/frontend.ts @@ -1,21 +1,21 @@ /** * External dependencies */ -import { store, navigate } from '@woocommerce/interactivity'; +import { store, navigate, getContext } from '@woocommerce/interactivity'; import { formatPrice, getCurrency } from '@woocommerce/price-format'; import { HTMLElementEvent } from '@woocommerce/types'; /** * Internal dependencies */ -import { PriceFilterState } from './types'; +import type { PriceFilterContext, PriceFilterStore } from './types'; -const getHrefWithFilters = ( state: PriceFilterState ) => { - const { minPrice = 0, maxPrice = 0, maxRange = 0 } = state; +const getUrl = ( context: PriceFilterContext ) => { + const { minPrice, maxPrice, minRange, maxRange } = context; const url = new URL( window.location.href ); const { searchParams } = url; - if ( minPrice > 0 ) { + if ( minPrice > minRange ) { searchParams.set( 'min_price', minPrice.toString() ); } else { searchParams.delete( 'min_price' ); @@ -34,80 +34,73 @@ const getHrefWithFilters = ( state: PriceFilterState ) => { return url.href; }; -interface PriceFilterStore { - state: PriceFilterState; - actions: { - setMinPrice: ( event: HTMLElementEvent< HTMLInputElement > ) => void; - setMaxPrice: ( event: HTMLElementEvent< HTMLInputElement > ) => void; - updateProducts: () => void; - reset: () => void; - }; -} +store< PriceFilterStore >( 'woocommerce/collection-price-filter', { + state: { + rangeStyle: () => { + const { minPrice, maxPrice, minRange, maxRange } = + getContext< PriceFilterContext >(); -const { state } = store< PriceFilterStore >( - 'woocommerce/collection-price-filter', - { - state: { - get rangeStyle(): string { - const { - minPrice = 0, - maxPrice = 0, - minRange = 0, - maxRange = 0, - } = state; - return [ - `--low: ${ - ( 100 * ( minPrice - minRange ) ) / - ( maxRange - minRange ) - }%`, - `--high: ${ - ( 100 * ( maxPrice - minRange ) ) / - ( maxRange - minRange ) - }%`, - ].join( ';' ); - }, - get formattedMinPrice(): string { - const { minPrice = 0 } = state; - return formatPrice( minPrice, getCurrency( { minorUnit: 0 } ) ); - }, - get formattedMaxPrice(): string { - const { maxPrice = 0 } = state; - return formatPrice( maxPrice, getCurrency( { minorUnit: 0 } ) ); - }, + return [ + `--low: ${ + ( 100 * ( minPrice - minRange ) ) / ( maxRange - minRange ) + }%`, + `--high: ${ + ( 100 * ( maxPrice - minRange ) ) / ( maxRange - minRange ) + }%`, + ].join( ';' ); }, - actions: { - setMinPrice: ( event: HTMLElementEvent< HTMLInputElement > ) => { - const { minRange = 0, maxPrice = 0, maxRange = 0 } = state; - const value = parseFloat( event.target.value ); - state.minPrice = Math.min( - Number.isNaN( value ) ? minRange : value, - maxRange - 1 - ); - state.maxPrice = Math.max( maxPrice, state.minPrice + 1 ); - }, - setMaxPrice: ( event: HTMLElementEvent< HTMLInputElement > ) => { - const { - minRange = 0, - minPrice = 0, - maxPrice = 0, - maxRange = 0, - } = state; - const value = parseFloat( event.target.value ); - state.maxPrice = Math.max( - Number.isNaN( value ) ? maxRange : value, - minRange + 1 - ); - state.minPrice = Math.min( minPrice, maxPrice - 1 ); - }, - updateProducts: () => { - navigate( getHrefWithFilters( state ) ); - }, - reset: () => { - const { maxRange = 0 } = state; - state.minPrice = 0; - state.maxPrice = maxRange; - navigate( getHrefWithFilters( state ) ); - }, + formattedMinPrice: () => { + const { minPrice } = getContext< PriceFilterContext >(); + return formatPrice( minPrice, getCurrency( { minorUnit: 0 } ) ); }, - } -); + formattedMaxPrice: () => { + const { maxPrice } = getContext< PriceFilterContext >(); + return formatPrice( maxPrice, getCurrency( { minorUnit: 0 } ) ); + }, + }, + actions: { + updateProducts: ( event: HTMLElementEvent< HTMLInputElement > ) => { + const context = getContext< PriceFilterContext >(); + const { minRange, minPrice, maxPrice, maxRange } = context; + const type = event.target.name; + const value = parseFloat( event.target.value ); + + const currentMinPrice = + type === 'min' + ? Math.min( + Number.isNaN( value ) ? minRange : value, + maxRange - 1 + ) + : minPrice; + const currentMaxPrice = + type === 'max' + ? Math.max( + Number.isNaN( value ) ? maxRange : value, + minRange + 1 + ) + : maxPrice; + + context.minPrice = currentMinPrice; + context.maxPrice = currentMaxPrice; + + navigate( + getUrl( { + minRange, + maxRange, + minPrice: currentMinPrice, + maxPrice: currentMaxPrice, + } ) + ); + }, + reset: () => { + navigate( + getUrl( { + minRange: 0, + maxRange: 0, + minPrice: 0, + maxPrice: 0, + } ) + ); + }, + }, +} ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/types.ts b/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/types.ts index 68100f02666..325aed671c4 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/types.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/collection-filters/inner-blocks/price-filter/types.ts @@ -2,6 +2,7 @@ * External dependencies */ import { BlockEditProps } from '@wordpress/blocks'; +import { HTMLElementEvent } from '@woocommerce/types'; export type BlockAttributes = { showInputFields: boolean; @@ -11,11 +12,26 @@ export type BlockAttributes = { export type EditProps = BlockEditProps< BlockAttributes >; export type PriceFilterState = { - minPrice?: number; - maxPrice?: number; - minRange?: number; - maxRange?: number; - rangeStyle: string; - formattedMinPrice: string; - formattedMaxPrice: string; + rangeStyle: () => string; + formattedMinPrice: () => string; + formattedMaxPrice: () => string; +}; + +export type PriceFilterContext = { + minPrice: number; + maxPrice: number; + minRange: number; + maxRange: number; +}; + +export type FilterComponentProps = BlockEditProps< BlockAttributes > & { + collectionData: Partial< PriceFilterState >; +}; + +export type PriceFilterStore = { + state: PriceFilterState; + actions: { + updateProducts: ( event: HTMLElementEvent< HTMLInputElement > ) => void; + reset: () => void; + }; }; diff --git a/plugins/woocommerce/changelog/42980-fix-42976-price-filter-convert-state-to-context b/plugins/woocommerce/changelog/42980-fix-42976-price-filter-convert-state-to-context new file mode 100644 index 00000000000..8370cfd9ba7 --- /dev/null +++ b/plugins/woocommerce/changelog/42980-fix-42976-price-filter-convert-state-to-context @@ -0,0 +1,4 @@ +Significance: patch +Type: update +Comment: Convert the Price Filter `state` to `context` to enhance its reactivity. + diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/CollectionPriceFilter.php b/plugins/woocommerce/src/Blocks/BlockTypes/CollectionPriceFilter.php index 423c8d7dea0..28733137075 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/CollectionPriceFilter.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/CollectionPriceFilter.php @@ -121,7 +121,6 @@ final class CollectionPriceFilter extends AbstractBlock { $price_range = $block->context['collectionData']['price_range']; - $wrapper_attributes = get_block_wrapper_attributes(); $min_range = $price_range['min_price'] / 10 ** $price_range['currency_minor_unit']; $max_range = $price_range['max_price'] / 10 ** $price_range['currency_minor_unit']; $min_price = intval( get_query_var( self::MIN_PRICE_QUERY_VAR, $min_range ) ); @@ -156,12 +155,10 @@ final class CollectionPriceFilter extends AbstractBlock { $__high = 100 * ( $max_price - $min_range ) / ( $max_range - $min_range ); $range_style = "--low: $__low%; --high: $__high%"; - $data_directive = wp_json_encode( array( 'namespace' => 'woocommerce/collection-price-filter' ) ); - $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $show_input_fields && $inline_input ? 'inline-input' : '', - 'data-wc-interactive' => $data_directive, + 'data-wc-interactive' => wp_json_encode( array( 'namespace' => 'woocommerce/collection-price-filter' ) ), ) ); @@ -171,8 +168,7 @@ final class CollectionPriceFilter extends AbstractBlock { class="min" type="text" value="%d" - data-wc-bind--value="state.minPrice" - data-wc-on--input="actions.setMinPrice" + data-wc-bind--value="context.minPrice" data-wc-on--change="actions.updateProducts" />', esc_attr( $min_price ) @@ -188,8 +184,7 @@ final class CollectionPriceFilter extends AbstractBlock { class="max" type="text" value="%d" - data-wc-bind--value="state.maxPrice" - data-wc-on--input="actions.setMaxPrice" + data-wc-bind--value="context.maxPrice" data-wc-on--change="actions.updateProducts" />', esc_attr( $max_price ) @@ -202,41 +197,43 @@ final class CollectionPriceFilter extends AbstractBlock { ob_start(); ?>
> -
-
- +
- -
-
- - - +
+ + +
+
+ + + +