Skip to content

Commit

Permalink
QueryInspectorControls: avoid rerender of TaxonomyControls on every k…
Browse files Browse the repository at this point in the history
…eystroke
  • Loading branch information
jsnajdr committed Sep 29, 2022
1 parent a6e9a24 commit 3366738
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ import { useEffect, useState, useCallback } from '@wordpress/element';
import OrderControl from './order-control';
import AuthorControl from './author-control';
import ParentControl from './parent-control';
import { TaxonomyControls, useTaxonomiesInfo } from './taxonomy-controls';
import { TaxonomyControls } from './taxonomy-controls';
import StickyControl from './sticky-control';
import {
usePostTypes,
useIsPostTypeHierarchical,
useAllowedControls,
isControlAllowed,
useTaxonomies,
} from '../../utils';

export default function QueryInspectorControls( {
Expand All @@ -50,7 +51,7 @@ export default function QueryInspectorControls( {
const allowedControls = useAllowedControls( attributes );
const [ showSticky, setShowSticky ] = useState( postType === 'post' );
const { postTypesTaxonomiesMap, postTypesSelectOptions } = usePostTypes();
const taxonomiesInfo = useTaxonomiesInfo( postType );
const taxonomies = useTaxonomies( postType );
const isPostTypeHierarchical = useIsPostTypeHierarchical( postType );
useEffect( () => {
setShowSticky( postType === 'post' );
Expand Down Expand Up @@ -192,7 +193,7 @@ export default function QueryInspectorControls( {
setQuerySearch( '' );
} }
>
{ !! taxonomiesInfo?.length &&
{ !! taxonomies?.length &&
isControlAllowed( allowedControls, 'taxQuery' ) && (
<ToolsPanelItem
label={ __( 'Taxonomies' ) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ import { store as coreStore } from '@wordpress/core-data';
/**
* Internal dependencies
*/
import { getEntitiesInfo, useTaxonomies } from '../../utils';
import { useTaxonomies } from '../../utils';
import { MAX_FETCHED_TERMS } from '../../constants';

// Helper function to get the term id based on user input in terms `FormTokenField`.
const getTermIdByTermValue = ( termsMappedByName, termValue ) => {
const getTermIdByTermValue = ( terms, termValue ) => {
// First we check for exact match by `term.id` or case sensitive `term.name` match.
const termId = termValue?.id || termsMappedByName[ termValue ]?.id;
if ( termId ) return termId;
const termId =
termValue?.id || terms.find( ( term ) => term.name === termValue )?.id;
if ( termId ) {
return termId;
}

/**
* Here we make an extra check for entered terms in a non case sensitive way,
* to match user expectations, due to `FormTokenField` behaviour that shows
Expand All @@ -26,100 +30,91 @@ const getTermIdByTermValue = ( termsMappedByName, termValue ) => {
* In this edge case we always apply the first match from the terms list.
*/
const termValueLower = termValue.toLocaleLowerCase();
for ( const term in termsMappedByName ) {
if ( term.toLocaleLowerCase() === termValueLower ) {
return termsMappedByName[ term ].id;
}
}
return terms.find(
( term ) => term.name.toLocaleLowerCase() === termValueLower
)?.id;
};

export const useTaxonomiesInfo = ( postType ) => {
const taxonomies = useTaxonomies( postType );
const taxonomiesInfo = useSelect(
const useTaxonomyTerms = ( slug ) => {
return useSelect(
( select ) => {
const { getEntityRecords } = select( coreStore );
const termsQuery = { context: 'view', per_page: MAX_FETCHED_TERMS };
const _taxonomiesInfo = taxonomies?.map( ( { slug, name } ) => {
const _terms = getEntityRecords( 'taxonomy', slug, termsQuery );
return {
slug,
name,
terms: getEntitiesInfo( _terms ),
};
} );
return _taxonomiesInfo;
const terms = select( coreStore ).getEntityRecords(
'taxonomy',
slug,
{ context: 'view', per_page: MAX_FETCHED_TERMS }
);
return { terms };
},
[ taxonomies ]
[ slug ]
);
return taxonomiesInfo;
};

export function TaxonomyControls( { onChange, query } ) {
const taxonomiesInfo = useTaxonomiesInfo( query.postType );
const onTermsChange = ( taxonomySlug ) => ( newTermValues ) => {
const taxonomyInfo = taxonomiesInfo.find(
( { slug } ) => slug === taxonomySlug
);
if ( ! taxonomyInfo ) return;
const termIds = Array.from(
newTermValues.reduce( ( accumulator, termValue ) => {
const termId = getTermIdByTermValue(
taxonomyInfo.terms.mapByName,
termValue
const { postType, taxQuery } = query;

const taxonomies = useTaxonomies( postType );
if ( ! taxonomies || taxonomies.length === 0 ) {
return null;
}

return (
<>
{ taxonomies.map( ( taxonomy ) => {
const value = taxQuery?.[ taxonomy.slug ] || [];
const handleChange = ( newTermIds ) =>
onChange( {
taxQuery: {
...taxQuery,
[ taxonomy.slug ]: newTermIds,
},
} );

return (
<TaxonomyItem
key={ taxonomy.slug }
taxonomy={ taxonomy }
value={ value }
onChange={ handleChange }
/>
);
if ( termId ) accumulator.add( termId );
return accumulator;
}, new Set() )
);
const newTaxQuery = {
...query.taxQuery,
[ taxonomySlug ]: termIds,
};
onChange( { taxQuery: newTaxQuery } );
} ) }
</>
);
}
function TaxonomyItem( { taxonomy, value, onChange } ) {
const { terms } = useTaxonomyTerms( taxonomy.slug );
if ( ! terms?.length ) {
return null;
}

const onTermsChange = ( newTermValues ) => {
const termIds = new Set();
for ( const termValue of newTermValues ) {
const termId = getTermIdByTermValue( terms, termValue );
if ( termId ) {
termIds.add( termId );
}
}

onChange( Array.from( termIds ) );
};
// Returns only the existing term ids in proper format to be

// Selects only the existing term ids in proper format to be
// used in `FormTokenField`. This prevents the component from
// crashing in the editor, when non existing term ids were provided.
const getExistingTaxQueryValue = ( taxonomySlug ) => {
const taxonomyInfo = taxonomiesInfo.find(
( { slug } ) => slug === taxonomySlug
);
if ( ! taxonomyInfo ) return [];
return ( query.taxQuery?.[ taxonomySlug ] || [] ).reduce(
( accumulator, termId ) => {
const term = taxonomyInfo.terms.mapById[ termId ];
if ( term ) {
accumulator.push( {
id: termId,
value: term.name,
} );
}
return accumulator;
},
[]
);
};
const taxQueryValue = value
.map( ( termId ) => terms.find( ( t ) => t.id === termId ) )
.filter( Boolean )
.map( ( term ) => ( { id: term.id, value: term.name } ) );

return (
<>
{ !! taxonomiesInfo?.length &&
taxonomiesInfo.map( ( { slug, name, terms } ) => {
if ( ! terms?.names?.length ) {
return null;
}
return (
<div
key={ slug }
className="block-library-query-inspector__taxonomy-control"
>
<FormTokenField
label={ name }
value={ getExistingTaxQueryValue( slug ) }
suggestions={ terms.names }
onChange={ onTermsChange( slug ) }
/>
</div>
);
} ) }
</>
<div className="block-library-query-inspector__taxonomy-control">
<FormTokenField
label={ taxonomy.name }
value={ taxQueryValue }
suggestions={ terms.map( ( t ) => t.name ) }
onChange={ onTermsChange }
/>
</div>
);
}

0 comments on commit 3366738

Please sign in to comment.