diff --git a/docgen/src/guide/Conditional_display.md b/docgen/src/guide/Conditional_display.md
index da3063393b..7d4a0c6356 100644
--- a/docgen/src/guide/Conditional_display.md
+++ b/docgen/src/guide/Conditional_display.md
@@ -6,55 +6,45 @@ category: guide
navWeight: 40
---
-Using our connector and [`createConnector`](guide/Custom_connectors.html) approach, you can conditionally display content based on the search state.
+When no results are found you might want to display some specific contents helping the user go back
+to a search that was successful.
+
+To help you do conditional rendering based on the `searchState` and the
+`searchResults` of InstantSearch, we provide the [`connectStateResults`](connectors/connectStateResults.html) connector.
## Displaying content when the query is empty
```jsx
-const Content = createConnector({
- displayName: 'ConditionalQuery',
- getProvidedProps(props, searchState) {
- return {query: searchState.query};
- },
- })(({query}) => {
- const content = query
- ?
The query {query} exists
- : No query
;
- return {content}
;
- });
+const Content = connectStateResults(
+ ({ searchState }) =>
+ searchState && searchState.query
+ ?
+ The query {searchState.query} exists
+
+ : No query
+);
```
## Displaying content when there's no results
```jsx
-const content = createConnector({
- displayName: 'ConditionalResults',
- getProvidedProps(props, searchState, searchResults) {
- const noResults = searchResults.results ? searchResults.results.nbHits === 0 : false;
- return {query: searchState.query, noResults};
- },
- })(({noResults, query}) => {
- const content = noResults
- ? No results found for {query}
- : Some results
;
- return {content}
;
- });
+const Content = connectStateResults(
+ ({ searchState, searchResults }) =>
+ searchResults && searchResults.nbHits !== 0
+ ? Some results
+ :
+ No results has been found for {searchState.query}
+
+);
```
## Displaying content when there's an error
```jsx
-const content = createConnector({
- displayName: 'ConditionalError',
- getProvidedProps(props, searchState, searchResults) {
- return {error: searchResults.error};
- },
- })(({error}) => {
- const content = error
- ? An error occurred: {error.message}
- : Some results
;
- return {content}
;
- });
+const Content = connectStateResults(
+ ({ error }) =>
+ error ? Some error
: No error
+);
```
## Displaying content when loading
@@ -62,35 +52,93 @@ const content = createConnector({
In slow user network situations you might want to know when the search results are loading.
```jsx
-const content = createConnector({
- displayName: 'ConditionalError',
- getProvidedProps(props, searchState, searchResults) {
- return {loading: searchResults.searching};
- },
-})(({loading}) => {
- const content = loading
- ? We are loading
- : Search finished
;
- return {content}
;
- });
+const Content = connectStateResults(
+ ({ searching }) =>
+ searching ? We are searching
: Search finished
+);
```
Alternatively, if you're using the search in List feature then you can know when the search results are loading by doing:
```jsx
-const content = createConnector({
- displayName: 'ConditionalError',
- getProvidedProps(props, searchState, searchResults) {
- return {loading: searchResults.searchingForFacetValues};
- },
-})(({loading}) => {
- const content = loading
- ? We are loading
- : Search finished
;
- return {content}
;
- });
+const Content = connectStateResults(
+ ({ searchingForFacetValues }) =>
+ searchingForFacetValues ? We are searching
: Search finished
+);
+```
+
+## Conditional display when dealing with multi indices
+
+If you're using the `` API and want to apply some conditional rendering you have access to the `searchResults` but also to all the results of every used indices looking at `allSearchResults`.
+
+```jsx
+const App = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+const IndexResults = connectStateResults(
+ ({ searchState, searchResults, children }) =>
+ searchResults && searchResults.nbHits !== 0 ? (
+ children
+ ) : (
+
+ No results has been found for {searchState.query} and index{' '}
+ {searchResults ? searchResults.index : ''}
+
+ )
+);
+
+const AllResults = connectStateResults(({ allSearchResults, children }) => {
+ const noResults =
+ allSearchResults &&
+ Object.values(allSearchResults).reduce(
+ (acc, results) => results.nbHits === 0,
+ false
+ );
+ return noResults ? (
+
+
No results in category, products or brand
+
+
+
+
+ ) : (
+ children
+ );
+});
```
+
Previous:
← Custom Connectors
diff --git a/packages/react-instantsearch/connectors.js b/packages/react-instantsearch/connectors.js
index e259663f28..6817785b8f 100644
--- a/packages/react-instantsearch/connectors.js
+++ b/packages/react-instantsearch/connectors.js
@@ -46,3 +46,6 @@ export {
export { default as connectSortBy } from './src/connectors/connectSortBy.js';
export { default as connectStats } from './src/connectors/connectStats.js';
export { default as connectToggle } from './src/connectors/connectToggle.js';
+export {
+ default as connectStateResults,
+} from './src/connectors/connectStateResults.js';
diff --git a/packages/react-instantsearch/src/connectors/connectHits.js b/packages/react-instantsearch/src/connectors/connectHits.js
index 3d2d6bc9c0..985e464258 100644
--- a/packages/react-instantsearch/src/connectors/connectHits.js
+++ b/packages/react-instantsearch/src/connectors/connectHits.js
@@ -19,8 +19,9 @@ import { getResults } from '../core/indexUtils';
* @example
* import React from 'react';
*
- * import { connectHits, Highlight, InstantSearch } from 'react-instantsearch/dom';
- *
+ * import { Highlight, InstantSearch } from 'react-instantsearch/dom';
+ * import { connectHits } from 'react-instantsearch/connectors';
+
* const CustomHits = connectHits(({ hits }) =>
*
* {hits.map(hit =>
diff --git a/packages/react-instantsearch/src/connectors/connectStateResults.js b/packages/react-instantsearch/src/connectors/connectStateResults.js
new file mode 100644
index 0000000000..e293f862a0
--- /dev/null
+++ b/packages/react-instantsearch/src/connectors/connectStateResults.js
@@ -0,0 +1,62 @@
+import createConnector from '../core/createConnector';
+import { getResults } from '../core/indexUtils';
+
+/**
+ * The `connectStateResults` connector provides a way to access the `searchState` and the `searchResults`
+ * of InstantSearch.
+ * For instance this connector allows you to create results/noResults or query/noQuery pages.
+ * @name connectStateResults
+ * @kind connector
+ * @providedPropType {object} searchState - The search state of the instant search component.
See: [Search state structure](https://community.algolia.com/react-instantsearch/guide/Search_state.html)
+ * @providedPropType {object} searchResults - The search results.
In case of multiple indices: if used under `
`, results will be those of the corresponding index otherwise it'll be those of the root index See: [Search results structure](https://community.algolia.com/algoliasearch-helper-js/reference.html#searchresults)
+ * @providedPropType {object} allSearchResults - In case of multiple indices you can retrieve all the results
+ * @providedPropType {string} error - If the search failed, the error will be logged here.
+ * @providedPropType {boolean} searching - If there is a search in progress.
+ * @providedPropType {boolean} searchingForFacetValues - If there is a search in a list in progress.
+ * @example
+ * import React from 'react';
+ *
+ * import { InstantSearch, Hits } from 'react-instantsearch/dom';
+ * import { connectStateResults } from 'react-instantsearch/connectors';
+ *
+ * const Content = connectStateResults(
+ * ({ searchState, searchResults }) =>
+ * searchResults && searchResults.nbHits !== 0
+ * ?
+ * :
+ * No results has been found for {searchState.query}
+ *
+ * );
+ * return (
+ *
+ *
+ *
+ * );
+ *
+ * export default function App() {
+ * return (
+ *
+ *
+ *
+ * );
+ * }
+ */
+export default createConnector({
+ displayName: 'AlgoliaStateResults',
+
+ getProvidedProps(props, searchState, searchResults) {
+ const results = getResults(searchResults, this.context);
+ return {
+ searchState,
+ searchResults: results,
+ allSearchResults: searchResults.results,
+ searching: searchResults.searching,
+ error: searchResults.error,
+ searchingForFacetValues: searchResults.searchingForFacetValues,
+ };
+ },
+});
diff --git a/packages/react-instantsearch/src/connectors/connectStateResults.test.js b/packages/react-instantsearch/src/connectors/connectStateResults.test.js
new file mode 100644
index 0000000000..ad04071e6c
--- /dev/null
+++ b/packages/react-instantsearch/src/connectors/connectStateResults.test.js
@@ -0,0 +1,69 @@
+/* eslint-env jest, jasmine */
+
+import connect from './connectStateResults';
+jest.mock('../core/createConnector');
+
+let props;
+
+describe('connectStateResults', () => {
+ describe('single index', () => {
+ const context = { context: { ais: { mainTargetedIndex: 'index' } } };
+ const getProvidedProps = connect.getProvidedProps.bind(context);
+ it('provides the correct props to the component', () => {
+ const searchState = { state: 'state' };
+ const error = 'error';
+ const searching = true;
+ const searchingForFacetValues = true;
+ const searchResults = {
+ results: { nbHits: 25, hits: [] },
+ error,
+ searching,
+ searchingForFacetValues,
+ };
+
+ props = getProvidedProps({}, searchState, searchResults);
+ expect(props).toEqual({
+ searchState,
+ searchResults: searchResults.results,
+ allSearchResults: searchResults.results,
+ error,
+ searching,
+ searchingForFacetValues,
+ });
+ });
+ });
+ describe('multi index', () => {
+ const context = {
+ context: {
+ ais: { mainTargetedIndex: 'first' },
+ multiIndexContext: { targetedIndex: 'first' },
+ },
+ };
+ const getProvidedProps = connect.getProvidedProps.bind(context);
+ it('provides the correct props to the component', () => {
+ const searchState = { state: 'state' };
+ const error = 'error';
+ const searching = true;
+ const searchingForFacetValues = true;
+ const searchResults = {
+ results: {
+ first: { nbHits: 25, hits: [] },
+ second: { nbHits: 25, hits: [] },
+ },
+ error,
+ searching,
+ searchingForFacetValues,
+ };
+
+ props = getProvidedProps({}, searchState, searchResults);
+ expect(props).toEqual({
+ searchState,
+ searchResults: searchResults.results.first,
+ allSearchResults: searchResults.results,
+ error,
+ searching,
+ searchingForFacetValues,
+ });
+ });
+ });
+});
diff --git a/packages/react-instantsearch/src/core/createConnector.js b/packages/react-instantsearch/src/core/createConnector.js
index 0af7c1d880..b75ddba38d 100644
--- a/packages/react-instantsearch/src/core/createConnector.js
+++ b/packages/react-instantsearch/src/core/createConnector.js
@@ -272,6 +272,32 @@ export default function createConnector(connectorDesc) {
}
: {};
+ if (process.env.NODE_ENV === 'development') {
+ const onlyGetProvidedPropsUsage = !Object.keys(connectorDesc).find(
+ key =>
+ [
+ 'getMetadata',
+ 'getSearchParameters',
+ 'refine',
+ 'cleanUp',
+ ].indexOf(key) > -1
+ );
+
+ if (
+ onlyGetProvidedPropsUsage &&
+ !connectorDesc.displayName.startsWith('Algolia')
+ ) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ 'react-instantsearch: it seems that you are using the `createConnector` api ' +
+ 'only to access the `searchState` and the `searchResults` through `getProvidedProps`.' +
+ 'We are now provided a dedicated API' +
+ ' the `connectStateResults` connector that you should use instead. The `createConnector` API will be ' +
+ 'soon deprecated and will break in future next major versions.'
+ );
+ }
+ }
+
return (
{
- const Content = createConnector({
- displayName: 'ConditionalResults',
- getProvidedProps(props, searchState, searchResults) {
- const noResults = searchResults.results
- ? searchResults.results.nbHits === 0
- : false;
- return { query: searchState.query, noResults };
- },
- })(({ noResults, query }) => {
- const content = noResults ? (
- No results has been found for {query}
- ) : (
- Some results
- );
- return {content}
;
- });
+ const Content = connectStateResults(
+ ({ searchState, searchResults }) =>
+ searchResults && searchResults.nbHits !== 0 ? (
+ Some results
+ ) : (
+ No results has been found for {searchState.query}
+ )
+ );
return (
@@ -30,19 +22,25 @@ stories
);
})
.add('NoQuery/HasQuery', () => {
- const Content = createConnector({
- displayName: 'ConditionalQuery',
- getProvidedProps(props, searchState) {
- return { query: searchState.query };
- },
- })(({ query }) => {
- const content = query ? (
- The query {query} exists
- ) : (
- No query
- );
- return {content}
;
- });
+ const Content = connectStateResults(
+ ({ searchState }) =>
+ searchState && searchState.query ? (
+ The query {searchState.query} exists
+ ) : (
+ No query
+ )
+ );
+ return (
+
+
+
+ );
+ })
+ .add('NoLoading/HasQuery', () => {
+ const Content = connectStateResults(
+ ({ searching }) =>
+ searching ? searching
: No searching
+ );
return (
diff --git a/stories/MultiIndex.stories.js b/stories/MultiIndex.stories.js
index 425023bad4..0cb0c0ee16 100644
--- a/stories/MultiIndex.stories.js
+++ b/stories/MultiIndex.stories.js
@@ -11,8 +11,10 @@ import {
import {
connectHits,
connectAutoComplete,
+ connectStateResults,
} from '../packages/react-instantsearch/connectors';
import Autosuggest from 'react-autosuggest';
+
const stories = storiesOf('', module);
stories
@@ -59,6 +61,50 @@ stories
+ ))
+ .add('with conditional rendering', () => (
+
+
+
+
+
+
));
const AutoComplete = connectAutoComplete(
@@ -129,3 +175,34 @@ const Product = ({ hit }) => {
Product.propTypes = {
hit: PropTypes.object.isRequired,
};
+
+const Content = connectStateResults(
+ ({ searchState, searchResults, children }) =>
+ searchResults && searchResults.nbHits !== 0 ? (
+ children
+ ) : (
+
+ No results has been found for {searchState.query} and index{' '}
+ {searchResults ? searchResults.index : ''}
+
+ )
+);
+
+const Results = connectStateResults(({ allSearchResults, children }) => {
+ const noResults =
+ allSearchResults &&
+ Object.values(allSearchResults).reduce(
+ (acc, results) => results.nbHits === 0,
+ false
+ );
+ return noResults ? (
+
+
No results in category, products or brand
+
+
+
+
+ ) : (
+ children
+ );
+});