diff --git a/modules/search/instant-search/components/scroll-button.jsx b/modules/search/instant-search/components/scroll-button.jsx
new file mode 100644
index 0000000000000..9e6fb0721b500
--- /dev/null
+++ b/modules/search/instant-search/components/scroll-button.jsx
@@ -0,0 +1,47 @@
+/** @jsx h */
+
+/**
+ * External dependencies
+ */
+import { h, Component } from 'preact';
+import { __ } from '@wordpress/i18n';
+// NOTE: We only import the debounce package here for to reduced bundle size.
+// Do not import the entire lodash library!
+// eslint-disable-next-line lodash/import-scope
+import debounce from 'lodash/debounce';
+
+class ScrollButton extends Component {
+ componentDidMount() {
+ this.props.enableAutoScrolling && document.addEventListener( 'scroll', this.checkScroll );
+ }
+ componentDidUnmount() {
+ document.removeEventListener( 'scroll', this.checkScroll );
+ }
+
+ checkScroll = debounce( () => {
+ if (
+ this.props.enableAutoScrolling &&
+ window.innerHeight + window.scrollY === document.body.offsetHeight
+ ) {
+ this.props.onClick();
+ }
+ }, 100 );
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default ScrollButton;
diff --git a/modules/search/instant-search/components/search-results.jsx b/modules/search/instant-search/components/search-results.jsx
index 61f57c077a792..a115e1fc685e7 100644
--- a/modules/search/instant-search/components/search-results.jsx
+++ b/modules/search/instant-search/components/search-results.jsx
@@ -10,6 +10,7 @@ import { h, Component } from 'preact';
* Internal dependencies
*/
import SearchResultMinimal from './search-result-minimal';
+import ScrollButton from './scroll-button';
class SearchResults extends Component {
render_result( result ) {
@@ -24,6 +25,7 @@ class SearchResults extends Component {
render() {
const { results = [], query, total = 0, corrected_query = false } = this.props;
+
if ( query === '' ) {
return
;
}
@@ -53,6 +55,13 @@ class SearchResults extends Component {
) }
{ results.map( result => this.render_result( result ) ) }
+ { this.props.hasNextPage && (
+
+ ) }
);
}
diff --git a/modules/search/instant-search/components/search-widget.jsx b/modules/search/instant-search/components/search-widget.jsx
index 7c591fd0bc8f1..9696a5514b2c3 100644
--- a/modules/search/instant-search/components/search-widget.jsx
+++ b/modules/search/instant-search/components/search-widget.jsx
@@ -31,13 +31,17 @@ class SearchApp extends Component {
super( ...arguments );
this.input = Preact.createRef();
this.requestId = 0;
+
+ // TODO: Rework these lines. We shouldn't reassign properties.
this.props.resultFormat = 'minimal';
this.props.aggregations = buildFilterAggregations( this.props.options.widgets );
this.props.widgets = this.props.options.widgets ? this.props.options.widgets : [];
+
this.state = {
+ isLoading: false,
query: this.props.initialValue,
+ response: {},
sort: this.props.initialSort,
- results: {},
};
this.getResults = debounce( this.getResults, 500 );
this.getResults( this.state.query, getFilterQuery(), this.state.sort );
@@ -72,30 +76,51 @@ class SearchApp extends Component {
this.getResults( this.state.query, getFilterQuery(), getSortQuery() );
};
- getResults = ( query, filter, sort ) => {
+ getResults = ( query, filter, sort, pageHandle ) => {
if ( query ) {
this.requestId++;
const requestId = this.requestId;
- search( {
- aggregations: this.props.aggregations,
- filter,
- query,
- resultFormat: this.props.options.resultFormat,
- siteId: this.props.options.siteId,
- sort,
- } ).then( results => {
- if ( this.requestId === requestId ) {
- this.setState( { results } );
- }
+ this.setState( { isLoading: true }, () => {
+ search( {
+ // Skip aggregations when requesting for paged results
+ aggregations: !! pageHandle ? this.props.aggregations : {},
+ filter,
+ pageHandle,
+ query,
+ resultFormat: this.props.options.resultFormat,
+ siteId: this.props.options.siteId,
+ sort,
+ } ).then( newResponse => {
+ if ( this.requestId === requestId ) {
+ const response = { ...newResponse };
+ if ( !! pageHandle ) {
+ response.results = [
+ ...( 'results' in this.state.response ? this.state.response.results : [] ),
+ ...newResponse.results,
+ ];
+ }
+ this.setState( { response } );
+ }
+ this.setState( { isLoading: false } );
+ } );
} );
} else {
- this.setState( { results: [] } );
+ this.setState( { response: {}, isLoading: false } );
}
};
+ loadNextPage = () => {
+ this.getResults(
+ this.state.query,
+ getFilterQuery(),
+ getSortQuery(),
+ this.state.response.page_handle
+ );
+ };
+
render() {
- const { query, results } = this.state;
+ const { query, response, isLoading } = this.state;
return (
{ this.props.widgets.map( ( widget, index ) => (
@@ -127,7 +152,7 @@ class SearchApp extends Component {
initialValues={ this.props.initialFilters }
onChange={ this.onChangeFilter }
postTypes={ this.props.options.postTypes }
- results={ this.state.results }
+ results={ this.state.response }
widget={ widget }
/>
@@ -136,9 +161,12 @@ class SearchApp extends Component {
diff --git a/modules/search/instant-search/lib/api.js b/modules/search/instant-search/lib/api.js
index c04c1f9b84519..d62e0f37d98f3 100644
--- a/modules/search/instant-search/lib/api.js
+++ b/modules/search/instant-search/lib/api.js
@@ -117,7 +117,7 @@ function buildFilterObject( filterQuery ) {
return filter;
}
-export function search( { aggregations, filter, query, resultFormat, siteId, sort } ) {
+export function search( { aggregations, filter, pageHandle, query, resultFormat, siteId, sort } ) {
let fields = [];
let highlight_fields = [];
switch ( resultFormat ) {
@@ -145,6 +145,7 @@ export function search( { aggregations, filter, query, resultFormat, siteId, sor
filter: buildFilterObject( filter ),
query: encodeURIComponent( query ),
sort,
+ page_handle: pageHandle,
} )
);