/**
* External dependencies
*/
import { usePrevious } from '@woocommerce/base-hooks';
import {
useQueryStateByKey,
useQueryStateByContext,
useCollectionData,
} from '@woocommerce/base-context/hooks';
import { useCallback, useState, useEffect } from '@wordpress/element';
import PriceSlider from '@woocommerce/base-components/price-slider';
import { useDebouncedCallback } from 'use-debounce';
import PropTypes from 'prop-types';
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
/**
* Internal dependencies
*/
import usePriceConstraints from './use-price-constraints.js';
/**
* Component displaying a price filter.
*
* @param {Object} props Component props.
* @param {Object} props.attributes Incoming block attributes.
* @param {boolean} props.isEditor Whether in editor context or not.
*/
const PriceFilterBlock = ( { attributes, isEditor = false } ) => {
const [ minPriceQuery, setMinPriceQuery ] = useQueryStateByKey(
'min_price',
null
);
const [ maxPriceQuery, setMaxPriceQuery ] = useQueryStateByKey(
'max_price',
null
);
const [ queryState ] = useQueryStateByContext();
const { results, isLoading } = useCollectionData( {
queryPrices: true,
queryState,
} );
const [ minPrice, setMinPrice ] = useState();
const [ maxPrice, setMaxPrice ] = useState();
const currency = getCurrencyFromPriceResponse( results.price_range );
const { minConstraint, maxConstraint } = usePriceConstraints( {
minPrice: results.price_range
? results.price_range.min_price
: undefined,
maxPrice: results.price_range
? results.price_range.max_price
: undefined,
minorUnit: currency.minorUnit,
} );
// Updates the query based on slider values.
const onSubmit = useCallback(
( newMinPrice, newMaxPrice ) => {
setMinPriceQuery(
newMinPrice === minConstraint ? undefined : newMinPrice
);
setMaxPriceQuery(
newMaxPrice === maxConstraint ? undefined : newMaxPrice
);
},
[ minConstraint, maxConstraint, setMinPriceQuery, setMaxPriceQuery ]
);
// Updates the query after a short delay.
const [ debouncedUpdateQuery ] = useDebouncedCallback( onSubmit, 500 );
// Callback when slider or input fields are changed.
const onChange = useCallback(
( prices ) => {
if ( prices[ 0 ] !== minPrice ) {
setMinPrice( prices[ 0 ] );
}
if ( prices[ 1 ] !== maxPrice ) {
setMaxPrice( prices[ 1 ] );
}
},
[ minPrice, maxPrice, setMinPrice, setMaxPrice ]
);
// Track price STATE changes - if state changes, update the query.
useEffect( () => {
if ( ! attributes.showFilterButton ) {
debouncedUpdateQuery( minPrice, maxPrice );
}
}, [
minPrice,
maxPrice,
attributes.showFilterButton,
debouncedUpdateQuery,
] );
// Track price query/price constraint changes so the slider reflects current filters.
const previousMinPriceQuery = usePrevious( minPriceQuery );
const previousMaxPriceQuery = usePrevious( maxPriceQuery );
const previousMinConstraint = usePrevious( minConstraint );
const previousMaxConstraint = usePrevious( maxConstraint );
useEffect( () => {
if (
! Number.isFinite( minPrice ) ||
( minPriceQuery !== previousMinPriceQuery && // minPrice from query changed
minPriceQuery !== minPrice ) || // minPrice from query doesn't match the UI min price
( minConstraint !== previousMinConstraint && // minPrice from query changed
minConstraint !== minPrice ) // minPrice from query doesn't match the UI min price
) {
setMinPrice(
Number.isFinite( minPriceQuery ) ? minPriceQuery : minConstraint
);
}
if (
! Number.isFinite( maxPrice ) ||
( maxPriceQuery !== previousMaxPriceQuery && // maxPrice from query changed
maxPriceQuery !== maxPrice ) || // maxPrice from query doesn't match the UI max price
( maxConstraint !== previousMaxConstraint && // maxPrice from query changed
maxConstraint !== maxPrice ) // maxPrice from query doesn't match the UI max price
) {
setMaxPrice(
Number.isFinite( maxPriceQuery ) ? maxPriceQuery : maxConstraint
);
}
}, [
minPrice,
maxPrice,
minPriceQuery,
maxPriceQuery,
minConstraint,
maxConstraint,
previousMinConstraint,
previousMaxConstraint,
previousMinPriceQuery,
previousMaxPriceQuery,
] );
if (
! isLoading &&
( minConstraint === null ||
maxConstraint === null ||
minConstraint === maxConstraint )
) {
return null;
}
const TagName = `h${ attributes.headingLevel }`;
return (
<>
{ ! isEditor && attributes.heading && (
<TagName>{ attributes.heading }</TagName>
) }
<div className="wc-block-price-slider">
<PriceSlider
minConstraint={ minConstraint }
maxConstraint={ maxConstraint }
minPrice={ minPrice }
maxPrice={ maxPrice }
currency={ currency }
showInputFields={ attributes.showInputFields }
showFilterButton={ attributes.showFilterButton }
onChange={ onChange }
onSubmit={ () => onSubmit( minPrice, maxPrice ) }
isLoading={ isLoading }
/>
</div>
</>
);
};
PriceFilterBlock.propTypes = {
/**
* The attributes for this block.
*/
attributes: PropTypes.object.isRequired,
/**
* Whether it's in the editor or frontend display.
*/
isEditor: PropTypes.bool,
};
export default PriceFilterBlock;