Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

Commit

Permalink
feat(Conditional): add connectStateResults connector (#357)
Browse files Browse the repository at this point in the history
* feat(Conditional): add connectStateResults connector

* feat(Conditiona): add warning and examples
  • Loading branch information
mthuret authored Sep 25, 2017
1 parent 485600c commit 462df5f
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 89 deletions.
162 changes: 105 additions & 57 deletions docgen/src/guide/Conditional_display.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,91 +6,139 @@ 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
? <div>The query {query} exists</div>
: <div>No query</div>;
return <div>{content}</div>;
});
const Content = connectStateResults(
({ searchState }) =>
searchState && searchState.query
? <div>
The query {searchState.query} exists
</div>
: <div>No query</div>
);
```

## 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
? <div>No results found for {query}</div>
: <div>Some results</div>;
return <div>{content}</div>;
});
const Content = connectStateResults(
({ searchState, searchResults }) =>
searchResults && searchResults.nbHits !== 0
? <div>Some results</div>
: <div>
No results has been found for {searchState.query}
</div>
);
```

## 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
? <div>An error occurred: {error.message}</div>
: <div>Some results</div>;
return <div>{content}</div>;
});
const Content = connectStateResults(
({ error }) =>
error ? <div>Some error</div> : <div>No error</div>
);
```

## Displaying content when loading

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
? <div>We are loading</div>
: <div>Search finished</div>;
return <div>{content}</div>;
});
const Content = connectStateResults(
({ searching }) =>
searching ? <div>We are searching</div> : <div>Search finished</div>
);
```

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
? <div>We are loading</div>
: <div>Search finished</div>;
return <div>{content}</div>;
});
const Content = connectStateResults(
({ searchingForFacetValues }) =>
searchingForFacetValues ? <div>We are searching</div> : <div>Search finished</div>
);
```

## Conditional display when dealing with multi indices

If you're using the `<Index>` 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 = () => (
<InstantSearch appId="" apiKey="" indexName="first">
<SearchBox />
<AllResults>
<div>
<Index indexName="first">
<IndexResults>
<div>
<div>first: </div>
<Hits />
</div>
</IndexResults>
</Index>
<Index indexName="second">
<IndexResults>
<div>
<div>second: </div>
<Hits />
</div>
</IndexResults>
</Index>
<Index indexName="third">
<IndexResults>
<div>
<div>third: </div>
<Hits />
</div>
</IndexResults>
</Index>
</div>
</AllResults>
</InstantSearch>
);

const IndexResults = connectStateResults(
({ searchState, searchResults, children }) =>
searchResults && searchResults.nbHits !== 0 ? (
children
) : (
<div>
No results has been found for {searchState.query} and index{' '}
{searchResults ? searchResults.index : ''}
</div>
)
);

const AllResults = connectStateResults(({ allSearchResults, children }) => {
const noResults =
allSearchResults &&
Object.values(allSearchResults).reduce(
(acc, results) => results.nbHits === 0,
false
);
return noResults ? (
<div>
<div>No results in category, products or brand</div>
<Index indexName="first" />
<Index indexName="second" />
<Index indexName="third" />
</div>
) : (
children
);
});
```


<div class="guide-nav">
<div class="guide-nav-left">
Previous: <a href="guide/Custom_connectors.html">← Custom Connectors</a>
Expand Down
3 changes: 3 additions & 0 deletions packages/react-instantsearch/connectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
5 changes: 3 additions & 2 deletions packages/react-instantsearch/src/connectors/connectHits.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) =>
* <div>
* {hits.map(hit =>
Expand Down
62 changes: 62 additions & 0 deletions packages/react-instantsearch/src/connectors/connectStateResults.js
Original file line number Diff line number Diff line change
@@ -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. <br/><br/> See: [Search state structure](https://community.algolia.com/react-instantsearch/guide/Search_state.html)
* @providedPropType {object} searchResults - The search results. <br/><br/> In case of multiple indices: if used under `<Index>`, 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
* ? <Hits/>
* : <div>
* No results has been found for {searchState.query}
* </div>
* );
* return (
* <WrapWithHits linkedStoryGroup="Conditional">
* <Content />
* </WrapWithHits>
* );
*
* export default function App() {
* return (
* <InstantSearch
* appId="latency"
* apiKey="6be0576ff61c053d5f9a3225e2a90f76"
* indexName="ikea"
* >
* <Content />
* </InstantSearch>
* );
* }
*/
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,
};
},
});
Original file line number Diff line number Diff line change
@@ -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,
});
});
});
});
26 changes: 26 additions & 0 deletions packages/react-instantsearch/src/core/createConnector.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Composed
{...this.props}
Expand Down
Loading

0 comments on commit 462df5f

Please sign in to comment.