-
Notifications
You must be signed in to change notification settings - Fork 219
Fix React hook dependency warnings in filter an All Products blocks #3285
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,7 +112,6 @@ const PriceSlider = ( { | |
minConstraint, | ||
maxConstraint, | ||
hasValidConstraints, | ||
stepValue, | ||
] ); | ||
|
||
/** | ||
|
@@ -186,7 +185,14 @@ const PriceSlider = ( { | |
parseInt( values[ 1 ], 10 ), | ||
] ); | ||
}, | ||
[ minPrice, maxPrice, minConstraint, maxConstraint, stepValue ] | ||
[ | ||
onChange, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar as before, but in this case it's a |
||
minPrice, | ||
maxPrice, | ||
minConstraint, | ||
maxConstraint, | ||
stepValue, | ||
] | ||
); | ||
|
||
/** | ||
|
@@ -221,14 +227,7 @@ const PriceSlider = ( { | |
parseInt( values[ 1 ], 10 ), | ||
] ); | ||
}, | ||
[ | ||
minConstraint, | ||
maxConstraint, | ||
stepValue, | ||
minPriceInput, | ||
maxPriceInput, | ||
currency, | ||
] | ||
[ onChange, stepValue, minPriceInput, maxPriceInput ] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
); | ||
|
||
const classes = classnames( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,7 +70,7 @@ const generateQuery = ( { sortValue, currentPage, attributes } ) => { | |
const extractPaginationAndSortAttributes = ( query ) => { | ||
/* eslint-disable-next-line no-unused-vars, camelcase */ | ||
const { order, orderby, page, per_page, ...totalQuery } = query; | ||
return totalQuery; | ||
return totalQuery || {}; | ||
}; | ||
|
||
const announceLoadingCompletion = ( totalProducts ) => { | ||
|
@@ -96,6 +96,11 @@ const announceLoadingCompletion = ( totalProducts ) => { | |
} | ||
}; | ||
|
||
const areQueryTotalsDifferent = ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I extracted that function outside of the functional component so it's not re-created on every render. This prevents |
||
{ totalQuery: nextQuery, totalProducts: nextProducts }, | ||
{ totalQuery: currentQuery } = {} | ||
) => ! isEqual( nextQuery, currentQuery ) && Number.isFinite( nextProducts ); | ||
|
||
const ProductList = ( { | ||
attributes, | ||
currentPage, | ||
|
@@ -129,28 +134,27 @@ const ProductList = ( { | |
// the total number of products is a finite number. | ||
const previousQueryTotals = usePrevious( | ||
{ totalQuery, totalProducts }, | ||
( | ||
{ totalQuery: nextQuery, totalProducts: nextProducts }, | ||
{ totalQuery: currentQuery } = {} | ||
) => | ||
! isEqual( nextQuery, currentQuery ) && | ||
Number.isFinite( nextProducts ) | ||
areQueryTotalsDifferent | ||
); | ||
const isPreviousTotalQueryEqual = | ||
typeof previousQueryTotals === 'object' && | ||
isEqual( totalQuery, previousQueryTotals.totalQuery ); | ||
|
||
// If query state (excluding pagination/sorting attributes) changed, | ||
// reset pagination to the first page. | ||
useEffect( () => { | ||
// If query state (excluding pagination/sorting attributes) changed, | ||
// reset pagination to the first page. | ||
if ( ! isPreviousTotalQueryEqual ) { | ||
onPageChange( 1 ); | ||
|
||
// Make sure there was a previous query, so we don't announce it on page load. | ||
if ( previousQueryTotals ) { | ||
announceLoadingCompletion( totalProducts ); | ||
} | ||
if ( isEqual( totalQuery, previousQueryTotals?.totalQuery ) ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as before. |
||
return; | ||
} | ||
onPageChange( 1 ); | ||
|
||
// Make sure there was a previous query, so we don't announce it on page load. | ||
if ( previousQueryTotals?.totalQuery ) { | ||
announceLoadingCompletion( totalProducts ); | ||
} | ||
}, [ queryState ] ); | ||
}, [ | ||
previousQueryTotals?.totalQuery, | ||
totalProducts, | ||
onPageChange, | ||
totalQuery, | ||
] ); | ||
|
||
const onPaginationChange = ( newPage ) => { | ||
scrollToTop( { focusableSelector: 'a, button' } ); | ||
|
@@ -175,7 +179,9 @@ const ProductList = ( { | |
const { contentVisibility } = attributes; | ||
const perPage = attributes.columns * attributes.rows; | ||
const totalPages = | ||
! Number.isFinite( totalProducts ) && isPreviousTotalQueryEqual | ||
! Number.isFinite( totalProducts ) && | ||
Number.isFinite( previousQueryTotals?.totalProducts ) && | ||
isEqual( totalQuery, previousQueryTotals?.totalQuery ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The check that was named |
||
? Math.ceil( previousQueryTotals.totalProducts / perPage ) | ||
: Math.ceil( totalProducts / perPage ); | ||
const listProducts = products.length | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ export const usePrevious = ( value, validation ) => { | |
) { | ||
ref.current = value; | ||
} | ||
}, [ value, ref.current ] ); | ||
}, [ value, validation ] ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing dep in a |
||
|
||
return ref.current; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ import { QUERY_STATE_STORE_KEY as storeKey } from '@woocommerce/block-data'; | |
import { useSelect, useDispatch } from '@wordpress/data'; | ||
import { useRef, useEffect, useCallback } from '@wordpress/element'; | ||
import { useQueryStateContext } from '@woocommerce/base-context'; | ||
import { usePrevious } from '@woocommerce/base-hooks'; | ||
import isShallowEqual from '@wordpress/is-shallow-equal'; | ||
import { assign } from 'lodash'; | ||
|
||
/** | ||
|
@@ -43,7 +45,7 @@ export const useQueryStateByContext = ( context ) => { | |
( value ) => { | ||
setValueForQueryContext( context, value ); | ||
}, | ||
[ context ] | ||
[ context, setValueForQueryContext ] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing dep in a |
||
); | ||
|
||
return [ queryState, setQueryState ]; | ||
|
@@ -56,11 +58,11 @@ export const useQueryStateByContext = ( context ) => { | |
* "Query State" is a wp.data store that keeps track of an arbitrary object of | ||
* query keys and their values. | ||
* | ||
* @param {*} queryKey The specific query key to retrieve the value for. | ||
* @param {*} defaultValue Default value if query does not exist. | ||
* @param {string} [context] What context to retrieve the query state for. If | ||
* not provided will attempt to use what is provided | ||
* by query state context. | ||
* @param {*} queryKey The specific query key to retrieve the value for. | ||
* @param {*} [defaultValue] Default value if query does not exist. | ||
* @param {string} [context] What context to retrieve the query state for. If | ||
* not provided will attempt to use what is provided | ||
* by query state context. | ||
* | ||
* @return {*} Whatever value is set at the query state index using the | ||
* provided context and query key. | ||
|
@@ -81,7 +83,7 @@ export const useQueryStateByKey = ( queryKey, defaultValue, context ) => { | |
( value ) => { | ||
setQueryValue( context, queryKey, value ); | ||
}, | ||
[ context, queryKey ] | ||
[ context, queryKey, setQueryValue ] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing dep in a |
||
); | ||
|
||
return [ queryValue, setQueryValueByKey ]; | ||
|
@@ -117,15 +119,31 @@ export const useSynchronizedQueryState = ( synchronizedQuery, context ) => { | |
const queryStateContext = useQueryStateContext(); | ||
context = context || queryStateContext; | ||
const [ queryState, setQueryState ] = useQueryStateByContext( context ); | ||
const currentQueryState = useShallowEqual( queryState ); | ||
const currentSynchronizedQuery = useShallowEqual( synchronizedQuery ); | ||
const previousSynchronizedQuery = usePrevious( currentSynchronizedQuery ); | ||
// used to ensure we allow initial synchronization to occur before | ||
// returning non-synced state. | ||
const isInitialized = useRef( false ); | ||
// update queryState anytime incoming synchronizedQuery changes | ||
useEffect( () => { | ||
setQueryState( assign( {}, queryState, currentSynchronizedQuery ) ); | ||
isInitialized.current = true; | ||
}, [ currentSynchronizedQuery ] ); | ||
if ( | ||
! isShallowEqual( | ||
previousSynchronizedQuery, | ||
currentSynchronizedQuery | ||
) | ||
) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only need to run this effect when |
||
setQueryState( | ||
assign( {}, currentQueryState, currentSynchronizedQuery ) | ||
); | ||
isInitialized.current = true; | ||
} | ||
}, [ | ||
currentQueryState, | ||
currentSynchronizedQuery, | ||
previousSynchronizedQuery, | ||
setQueryState, | ||
] ); | ||
return isInitialized.current | ||
? [ queryState, setQueryState ] | ||
: [ synchronizedQuery, setQueryState ]; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,7 +47,13 @@ const ActiveFiltersBlock = ( { | |
}, | ||
displayStyle: blockAttributes.displayStyle, | ||
} ); | ||
}, [ minPrice, maxPrice, formatPriceRange ] ); | ||
}, [ | ||
minPrice, | ||
maxPrice, | ||
blockAttributes.displayStyle, | ||
setMinPrice, | ||
setMaxPrice, | ||
] ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing deps in a |
||
|
||
const activeAttributeFilters = useMemo( () => { | ||
return productAttributes.map( ( attribute ) => { | ||
|
@@ -64,7 +70,7 @@ const ActiveFiltersBlock = ( { | |
/> | ||
); | ||
} ); | ||
}, [ productAttributes ] ); | ||
}, [ productAttributes, blockAttributes.displayStyle ] ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same. 🙂 |
||
|
||
const hasFilters = () => { | ||
return ( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import { | |
useQueryStateByKey, | ||
useQueryStateByContext, | ||
useCollectionData, | ||
usePrevious, | ||
useShallowEqual, | ||
} from '@woocommerce/base-hooks'; | ||
import { | ||
|
@@ -113,7 +114,7 @@ const AttributeFilterBlock = ( { | |
* @param {string} termSlug The term of the slug to check. | ||
*/ | ||
const isTermInQueryState = ( termSlug ) => { | ||
if ( ! queryState || ! queryState.attributes ) { | ||
if ( ! queryState?.attributes ) { | ||
return false; | ||
} | ||
return queryState.attributes.some( | ||
|
@@ -168,16 +169,11 @@ const AttributeFilterBlock = ( { | |
] ); | ||
|
||
// Track checked STATE changes - if state changes, update the query. | ||
useEffect( | ||
() => { | ||
if ( ! blockAttributes.showFilterButton ) { | ||
onSubmit(); | ||
} | ||
}, | ||
// There is no need to add blockAttributes.showFilterButton as a dependency. | ||
// It will only change in the editor and there we don't need to call onSubmit in any case. | ||
[ checked, onSubmit ] | ||
); | ||
useEffect( () => { | ||
if ( ! blockAttributes.showFilterButton ) { | ||
onSubmit( checked ); | ||
} | ||
}, [ blockAttributes.showFilterButton, checked, onSubmit ] ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing dep in effect. |
||
|
||
const checkedQuery = useMemo( () => { | ||
return productAttributesQuery | ||
|
@@ -188,17 +184,16 @@ const AttributeFilterBlock = ( { | |
}, [ productAttributesQuery, attributeObject.taxonomy ] ); | ||
|
||
const currentCheckedQuery = useShallowEqual( checkedQuery ); | ||
|
||
const previousCheckedQuery = usePrevious( currentCheckedQuery ); | ||
// Track ATTRIBUTES QUERY changes so the block reflects current filters. | ||
useEffect( | ||
() => { | ||
if ( ! isShallowEqual( checked, checkedQuery ) ) { | ||
setChecked( checkedQuery ); | ||
} | ||
}, | ||
// We only want to apply this effect when the query changes, so we are intentionally leaving `checked` out of the dependencies. | ||
[ currentCheckedQuery ] | ||
); | ||
useEffect( () => { | ||
if ( | ||
! isShallowEqual( previousCheckedQuery, currentCheckedQuery ) && // checked query changed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only want to run this effect when |
||
! isShallowEqual( checked, currentCheckedQuery ) // checked query doesn't match the UI | ||
) { | ||
setChecked( currentCheckedQuery ); | ||
} | ||
}, [ checked, currentCheckedQuery, previousCheckedQuery ] ); | ||
|
||
/** | ||
* Returns an array of term objects that have been chosen via the checkboxes. | ||
|
@@ -215,19 +210,29 @@ const AttributeFilterBlock = ( { | |
[ attributeTerms ] | ||
); | ||
|
||
const onSubmit = () => { | ||
if ( isEditor ) { | ||
return; | ||
} | ||
const onSubmit = useCallback( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since |
||
( isChecked ) => { | ||
if ( isEditor ) { | ||
return; | ||
} | ||
|
||
updateAttributeFilter( | ||
updateAttributeFilter( | ||
productAttributesQuery, | ||
setProductAttributesQuery, | ||
attributeObject, | ||
getSelectedTerms( isChecked ), | ||
blockAttributes.queryType === 'or' ? 'in' : 'and' | ||
); | ||
}, | ||
[ | ||
isEditor, | ||
productAttributesQuery, | ||
setProductAttributesQuery, | ||
attributeObject, | ||
getSelectedTerms( checked ), | ||
blockAttributes.queryType === 'or' ? 'in' : 'and' | ||
); | ||
}; | ||
getSelectedTerms, | ||
blockAttributes.queryType, | ||
] | ||
); | ||
|
||
const multiple = | ||
blockAttributes.displayStyle !== 'dropdown' || | ||
|
@@ -357,7 +362,7 @@ const AttributeFilterBlock = ( { | |
<FilterSubmitButton | ||
className="wc-block-attribute-filter__button" | ||
disabled={ isLoading || isDisabled } | ||
onClick={ onSubmit } | ||
onClick={ () => onSubmit( checked ) } | ||
/> | ||
) } | ||
</div> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one was easy: missing dependency in a
useMemo
.onChange
is not expected to change, but in order to fix the linting warning, it was needed.