-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
link-picker-results.native.js
129 lines (117 loc) · 3.76 KB
/
link-picker-results.native.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* 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>
);
}