From 89e573aad39416e43bba7f71ce92ea72501e5118 Mon Sep 17 00:00:00 2001 From: ntsekouras Date: Mon, 19 Dec 2022 13:55:43 +0200 Subject: [PATCH] [Block Library - Query Loop]: Fetch terms suggestions dynamically --- packages/block-library/src/query/constants.js | 7 - .../inspector-controls/taxonomy-controls.js | 132 ++++++++++++------ .../src/query/edit/query-content.js | 3 +- 3 files changed, 93 insertions(+), 49 deletions(-) delete mode 100644 packages/block-library/src/query/constants.js diff --git a/packages/block-library/src/query/constants.js b/packages/block-library/src/query/constants.js deleted file mode 100644 index 9f606c1c6920c3..00000000000000 --- a/packages/block-library/src/query/constants.js +++ /dev/null @@ -1,7 +0,0 @@ -export const MAX_FETCHED_TERMS = 100; -export const DEFAULTS_POSTS_PER_PAGE = 3; - -export default { - MAX_FETCHED_TERMS, - DEFAULTS_POSTS_PER_PAGE, -}; diff --git a/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js b/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js index 1cfee79a8e8001..81d393e446d98d 100644 --- a/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js +++ b/packages/block-library/src/query/edit/inspector-controls/taxonomy-controls.js @@ -4,12 +4,20 @@ import { FormTokenField } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; +import { useState, useEffect } from '@wordpress/element'; +import { useDebounce } from '@wordpress/compose'; /** * Internal dependencies */ import { useTaxonomies } from '../../utils'; -import { MAX_FETCHED_TERMS } from '../../constants'; + +const EMPTY_ARRAY = []; +const BASE_QUERY = { + order: 'asc', + _fields: 'id,name', + context: 'view', +}; // Helper function to get the term id based on user input in terms `FormTokenField`. const getTermIdByTermValue = ( terms, termValue ) => { @@ -35,20 +43,6 @@ const getTermIdByTermValue = ( terms, termValue ) => { )?.id; }; -const useTaxonomyTerms = ( slug ) => { - return useSelect( - ( select ) => { - const terms = select( coreStore ).getEntityRecords( - 'taxonomy', - slug, - { context: 'view', per_page: MAX_FETCHED_TERMS } - ); - return { terms }; - }, - [ slug ] - ); -}; - export function TaxonomyControls( { onChange, query } ) { const { postType, taxQuery } = query; @@ -73,7 +67,7 @@ export function TaxonomyControls( { onChange, query } ) { ); @@ -81,41 +75,97 @@ export function TaxonomyControls( { onChange, query } ) { ); } -function TaxonomyItem( { taxonomy, value, onChange } ) { - const { terms } = useTaxonomyTerms( taxonomy.slug ); - if ( ! terms?.length ) { - return null; - } +function TaxonomyItem( { taxonomy, terms, onChange } ) { + const [ search, setSearch ] = useState( '' ); + const [ value, setValue ] = useState( EMPTY_ARRAY ); + const [ suggestions, setSuggestions ] = useState( EMPTY_ARRAY ); + const debouncedSearch = useDebounce( setSearch, 250 ); + const { searchResults, searchHasResolved } = useSelect( + ( select ) => { + if ( ! search ) { + return { searchResults: EMPTY_ARRAY, searchHasResolved: true }; + } + const { getEntityRecords, hasFinishedResolution } = + select( coreStore ); + const selectorArgs = [ + 'taxonomy', + taxonomy.slug, + { + ...BASE_QUERY, + search, + orderby: 'name', + exclude: terms, + per_page: 20, + }, + ]; + return { + searchResults: getEntityRecords( ...selectorArgs ), + searchHasResolved: hasFinishedResolution( + 'getEntityRecords', + selectorArgs + ), + }; + }, + [ search, terms ] + ); + const currentTerms = useSelect( + ( select ) => { + if ( ! terms?.length ) return EMPTY_ARRAY; + const { getEntityRecords } = select( coreStore ); + return getEntityRecords( 'taxonomy', taxonomy.slug, { + ...BASE_QUERY, + include: terms, + per_page: terms.length, + } ); + }, + [ terms ] + ); + // Update the `value` state only after the selectors are resolved + // to avoid emptying the input when we're changing terms. + useEffect( () => { + if ( ! terms?.length ) { + setValue( EMPTY_ARRAY ); + } + if ( ! currentTerms?.length ) return; + // Returns only the existing entity ids. This prevents the component + // from crashing in the editor, when non existing ids are provided. + const sanitizedValue = terms.reduce( ( accumulator, id ) => { + const entity = currentTerms.find( ( term ) => term.id === id ); + if ( entity ) { + accumulator.push( { + id, + value: entity.name, + } ); + } + return accumulator; + }, [] ); + setValue( sanitizedValue ); + }, [ terms, currentTerms ] ); + // Update suggestions only when the query has resolved. + useEffect( () => { + if ( ! searchHasResolved ) return; + setSuggestions( searchResults.map( ( result ) => result.name ) ); + }, [ searchResults, searchHasResolved ] ); const onTermsChange = ( newTermValues ) => { const termIds = new Set(); for ( const termValue of newTermValues ) { - const termId = getTermIdByTermValue( terms, termValue ); + const termId = getTermIdByTermValue( searchResults, termValue ); if ( termId ) { termIds.add( termId ); } } - + setSuggestions( EMPTY_ARRAY ); onChange( Array.from( termIds ) ); }; - - // 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 taxQueryValue = value - .map( ( termId ) => terms.find( ( t ) => t.id === termId ) ) - .filter( Boolean ) - .map( ( term ) => ( { id: term.id, value: term.name } ) ); - return ( -
- t.name ) } - onChange={ onTermsChange } - __experimentalShowHowTo={ false } - /> -
+ ); } diff --git a/packages/block-library/src/query/edit/query-content.js b/packages/block-library/src/query/edit/query-content.js index bdb4dc3e734337..9cb8d8775a2bf3 100644 --- a/packages/block-library/src/query/edit/query-content.js +++ b/packages/block-library/src/query/edit/query-content.js @@ -20,7 +20,8 @@ import { __ } from '@wordpress/i18n'; */ import QueryToolbar from './query-toolbar'; import QueryInspectorControls from './inspector-controls'; -import { DEFAULTS_POSTS_PER_PAGE } from '../constants'; + +const DEFAULTS_POSTS_PER_PAGE = 3; const TEMPLATE = [ [ 'core/post-template' ] ]; export default function QueryContent( {