Skip to content
This repository was archived by the owner on Feb 23, 2024. It is now read-only.

Commit ebc3662

Browse files
authored
Fix: Convert Filter Products by Attribute Block to TypeScript (#6545)
* wip: convert attribute filter to ts * change query to optional * update SearchListControlProps and SearchListItemType * get default attribute from metadata * update types * convert attributes utils to ts * convert attribute query utils to ts * fix type error, remove type casting * revert type change for SearchListItemType * apply new format
1 parent df63d2a commit ebc3662

File tree

17 files changed

+305
-173
lines changed

17 files changed

+305
-173
lines changed

assets/js/base/context/hooks/collections/use-collection-data.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useState, useEffect, useMemo } from '@wordpress/element';
55
import { useDebounce } from 'use-debounce';
66
import { sortBy } from 'lodash';
77
import { useShallowEqual } from '@woocommerce/base-hooks';
8+
import { objectHasProp } from '@woocommerce/types';
89

910
/**
1011
* Internal dependencies
@@ -78,6 +79,7 @@ export const useCollectionData = ( {
7879
const foundAttribute = calculateAttributesQueryState.find(
7980
( attribute ) => {
8081
return (
82+
objectHasProp( currentQueryAttribute, 'taxonomy' ) &&
8183
attribute.taxonomy === currentQueryAttribute.taxonomy
8284
);
8385
}

assets/js/base/context/hooks/collections/use-collection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export interface useCollectionOptions {
4444
namespace: string;
4545
resourceName: string;
4646
resourceValues?: number[];
47-
query: Record< string, unknown >;
47+
query?: Record< string, unknown >;
4848
shouldSelect?: boolean;
4949
}
5050

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { __ } from '@wordpress/i18n';
5+
6+
export const blockAttributes = {
7+
heading: {
8+
type: 'string',
9+
default: __( 'Filter by attribute', 'woo-gutenberg-products-block' ),
10+
},
11+
};

assets/js/blocks/attribute-filter/block.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
}
1919
},
2020
"attributes": {
21+
"className": {
22+
"type": "string",
23+
"default": ""
24+
},
2125
"attributeId": {
2226
"type": "number",
2327
"default": 0

assets/js/blocks/attribute-filter/block.js renamed to assets/js/blocks/attribute-filter/block.tsx

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ import { Notice } from '@wordpress/components';
2121
import classNames from 'classnames';
2222
import { getSettingWithCoercion } from '@woocommerce/settings';
2323
import { getQueryArgs, removeQueryArgs } from '@wordpress/url';
24-
import { isBoolean, isString } from '@woocommerce/types';
24+
import {
25+
AttributeQuery,
26+
isBoolean,
27+
isString,
28+
objectHasProp,
29+
} from '@woocommerce/types';
2530
import {
2631
PREFIX_QUERY_ARG_FILTER_TYPE,
2732
PREFIX_QUERY_ARG_QUERY_TYPE,
@@ -42,6 +47,7 @@ import {
4247
isQueryArgsEqual,
4348
parseTaxonomyToGenerateURL,
4449
} from './utils';
50+
import { BlockAttributes, DisplayOption } from './types';
4551

4652
/**
4753
* Formats filter values into a string for the URL parameters needed for filtering PHP templates.
@@ -62,6 +68,9 @@ import {
6268
const AttributeFilterBlock = ( {
6369
attributes: blockAttributes,
6470
isEditor = false,
71+
}: {
72+
attributes: BlockAttributes;
73+
isEditor?: boolean;
6574
} ) => {
6675
const hasFilterableProducts = getSettingWithCoercion(
6776
'has_filterable_products',
@@ -93,7 +102,9 @@ const AttributeFilterBlock = ( {
93102
getActiveFilters( filteringForPhpTemplate, attributeObject )
94103
);
95104

96-
const [ displayedOptions, setDisplayedOptions ] = useState(
105+
const [ displayedOptions, setDisplayedOptions ] = useState<
106+
DisplayOption[]
107+
>(
97108
blockAttributes.isPreview && ! blockAttributes.attributeId
98109
? previewOptions
99110
: []
@@ -119,7 +130,7 @@ const AttributeFilterBlock = ( {
119130
const { results: filteredCounts, isLoading: filteredCountsLoading } =
120131
useCollectionData( {
121132
queryAttribute: {
122-
taxonomy: attributeObject?.taxonomy,
133+
taxonomy: attributeObject?.taxonomy || '',
123134
queryType: blockAttributes.queryType,
124135
},
125136
queryState: {
@@ -133,7 +144,10 @@ const AttributeFilterBlock = ( {
133144
*/
134145
const getFilteredTerm = useCallback(
135146
( id ) => {
136-
if ( ! filteredCounts.attribute_counts ) {
147+
if (
148+
! objectHasProp( filteredCounts, 'attribute_counts' ) ||
149+
! Array.isArray( filteredCounts.attribute_counts )
150+
) {
137151
return null;
138152
}
139153
return filteredCounts.attribute_counts.find(
@@ -152,13 +166,13 @@ const AttributeFilterBlock = ( {
152166
*
153167
* @param {string} termSlug The term of the slug to check.
154168
*/
155-
const isTermInQueryState = ( termSlug ) => {
169+
const isTermInQueryState = ( termSlug: string ) => {
156170
if ( ! queryState?.attributes ) {
157171
return false;
158172
}
159173
return queryState.attributes.some(
160-
( { attribute, slug = [] } ) =>
161-
attribute === attributeObject.taxonomy &&
174+
( { attribute, slug = [] }: AttributeQuery ) =>
175+
attribute === attributeObject?.taxonomy &&
162176
slug.includes( termSlug )
163177
);
164178
};
@@ -167,6 +181,10 @@ const AttributeFilterBlock = ( {
167181
return;
168182
}
169183

184+
if ( ! Array.isArray( attributeTerms ) ) {
185+
return;
186+
}
187+
170188
const newOptions = attributeTerms
171189
.map( ( term ) => {
172190
const filteredTerm = getFilteredTerm( term.id );
@@ -193,7 +211,7 @@ const AttributeFilterBlock = ( {
193211
),
194212
};
195213
} )
196-
.filter( Boolean );
214+
.filter( ( option ): option is DisplayOption => !! option );
197215

198216
setDisplayedOptions( newOptions );
199217
}, [
@@ -212,6 +230,9 @@ const AttributeFilterBlock = ( {
212230
*/
213231
const getSelectedTerms = useCallback(
214232
( newChecked ) => {
233+
if ( ! Array.isArray( attributeTerms ) ) {
234+
return [];
235+
}
215236
return attributeTerms.reduce( ( acc, term ) => {
216237
if ( newChecked.includes( term.slug ) ) {
217238
acc.push( term );
@@ -231,12 +252,15 @@ const AttributeFilterBlock = ( {
231252
const redirectPageForPhpTemplate = useCallback(
232253
( query, allFiltersRemoved = false ) => {
233254
if ( allFiltersRemoved ) {
255+
if ( ! attributeObject?.taxonomy ) {
256+
return;
257+
}
234258
const currentQueryArgKeys = Object.keys(
235259
getQueryArgs( window.location.href )
236260
);
237261

238262
const parsedTaxonomy = parseTaxonomyToGenerateURL(
239-
attributeObject?.taxonomy
263+
attributeObject.taxonomy
240264
);
241265

242266
const url = currentQueryArgKeys.reduce(
@@ -267,7 +291,7 @@ const AttributeFilterBlock = ( {
267291
[ pageUrl, attributeObject?.taxonomy ]
268292
);
269293

270-
const onSubmit = ( checkedFilters ) => {
294+
const onSubmit = ( checkedFilters: string[] ) => {
271295
const query = updateAttributeFilter(
272296
productAttributesQuery,
273297
setProductAttributesQuery,
@@ -283,7 +307,7 @@ const AttributeFilterBlock = ( {
283307
};
284308

285309
const updateCheckedFilters = useCallback(
286-
( checkedFilters ) => {
310+
( checkedFilters: string[] ) => {
287311
if ( isEditor ) {
288312
return;
289313
}
@@ -324,6 +348,7 @@ const AttributeFilterBlock = ( {
324348
// Track ATTRIBUTES QUERY changes so the block reflects current filters.
325349
useEffect( () => {
326350
if (
351+
previousCheckedQuery &&
327352
! isShallowEqual( previousCheckedQuery, currentCheckedQuery ) && // checked query changed
328353
! isShallowEqual( checked, currentCheckedQuery ) // checked query doesn't match the UI
329354
) {
@@ -345,15 +370,23 @@ const AttributeFilterBlock = ( {
345370
*/
346371
const onChange = useCallback(
347372
( checkedValue ) => {
348-
const getFilterNameFromValue = ( filterValue ) => {
349-
const { name } = displayedOptions.find(
373+
const getFilterNameFromValue = ( filterValue: string ) => {
374+
const result = displayedOptions.find(
350375
( option ) => option.value === filterValue
351376
);
352377

353-
return name;
378+
if ( result ) {
379+
return result.name;
380+
}
354381
};
355382

356-
const announceFilterChange = ( { filterAdded, filterRemoved } ) => {
383+
const announceFilterChange = ( {
384+
filterAdded,
385+
filterRemoved,
386+
}: {
387+
filterAdded?: string | null;
388+
filterRemoved?: string | null;
389+
} ) => {
357390
const filterAddedName = filterAdded
358391
? getFilterNameFromValue( filterAdded )
359392
: null;
@@ -526,7 +559,8 @@ const AttributeFilterBlock = ( {
526559
return null;
527560
}
528561

529-
const TagName = `h${ blockAttributes.headingLevel }`;
562+
const TagName =
563+
`h${ blockAttributes.headingLevel }` as keyof JSX.IntrinsicElements;
530564
const isLoading = ! blockAttributes.isPreview && attributeTermsLoading;
531565
const isDisabled = ! blockAttributes.isPreview && filteredCountsLoading;
532566

0 commit comments

Comments
 (0)