@wordpress/components
Version:
UI components for WordPress.
130 lines (117 loc) • 3.85 kB
JavaScript
/**
* External dependencies
*/
import { ActivityIndicator, FlatList, View } from 'react-native';
/**
* WordPress dependencies
*/
import { debounce } from '@wordpress/compose';
import { useState, useEffect, useRef } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import styles from './styles.scss';
import BottomSheet from '../bottom-sheet';
import { BottomSheetConsumer } from '../bottom-sheet/bottom-sheet-context';
const PER_PAGE = 20;
const REQUEST_DEBOUNCE_DELAY = 400;
const MINIMUM_QUERY_SIZE = 2;
const meetsThreshold = ( query ) => MINIMUM_QUERY_SIZE <= query.length;
export default function LinkPickerResults( {
query,
onLinkPicked,
directEntry,
} ) {
const [ links, setLinks ] = useState( [ directEntry ] );
const [ hasAllSuggestions, setHasAllSuggestions ] = useState( false );
const nextPage = useRef( 1 );
const pendingRequest = useRef();
const clearRequest = () => {
pendingRequest.current = null;
};
// A stable debounced function to fetch suggestions and append.
const { fetchMoreSuggestions } = useSelect( ( select ) => {
const { getSettings } = select( 'core/block-editor' );
const fetchLinkSuggestions = async ( { search } ) => {
if ( meetsThreshold( search ) ) {
return await getSettings().__experimentalFetchLinkSuggestions(
search,
{ page: nextPage.current, type: 'post', perPage: PER_PAGE }
);
}
};
const fetchMore = async ( {
query: search,
links: currentSuggestions,
} ) => {
// Return early if we've already detected the end of data or we are
// already awaiting a response.
if ( hasAllSuggestions || pendingRequest.current ) {
return;
}
const request = fetchLinkSuggestions( { search } );
pendingRequest.current = request;
const suggestions = await request;
// Only update links for the most recent request.
if ( suggestions && request === pendingRequest.current ) {
// Since we don't have the response header, we check if the results
// are truncated to determine we've reached the end.
if ( suggestions.length < PER_PAGE ) {
setHasAllSuggestions( true );
}
setLinks( [ ...currentSuggestions, ...suggestions ] );
nextPage.current++;
}
clearRequest();
};
return {
fetchMoreSuggestions: debounce( fetchMore, REQUEST_DEBOUNCE_DELAY ),
};
// Not adding dependencies for now, to avoid introducing a regression
// (see https://github.com/WordPress/gutenberg/pull/23922#discussion_r1170634879).
}, [] );
// Prevent setting state when unmounted.
useEffect( () => clearRequest, [] );
// Any time the query changes, we reset pagination.
useEffect( () => {
clearRequest();
nextPage.current = 1;
setHasAllSuggestions( false );
setLinks( [ directEntry ] );
fetchMoreSuggestions( { query, links: [ directEntry ] } );
// See https://github.com/WordPress/gutenberg/pull/41166
}, [ query ] );
const onEndReached = () => fetchMoreSuggestions( { query, links } );
const spinner = ! hasAllSuggestions && meetsThreshold( query ) && (
<View style={ styles.spinner } testID="link-picker-loading">
<ActivityIndicator animating />
</View>
);
return (
<BottomSheetConsumer>
{ ( { listProps } ) => (
<FlatList
data={ links }
keyboardShouldPersistTaps="always"
renderItem={ ( { item } ) => (
<BottomSheet.LinkSuggestionItemCell
suggestion={ item }
onLinkPicked={ onLinkPicked }
/>
) }
keyExtractor={ ( { url, type } ) => `${ url }-${ type }` }
onEndReached={ onEndReached }
onEndReachedThreshold={ 0.1 }
initialNumToRender={ PER_PAGE }
ListFooterComponent={ spinner }
{ ...listProps }
contentContainerStyle={ [
...listProps.contentContainerStyle,
styles.list,
] }
/>
) }
</BottomSheetConsumer>
);
}