-
Notifications
You must be signed in to change notification settings - Fork 386
feat(Conditional): add connectStateResults connector #357
Changes from 3 commits
8568c1b
a560527
7b6c276
aebfafc
f8786d5
e9425be
16c18e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,89 +6,65 @@ 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we have an example where we use data from multiple indices? I think we said something like "searchResults, allSearchResults".? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes I can add the code of the story for multi indices here 👍 |
||
|
||
```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> | ||
); | ||
``` | ||
|
||
<div class="guide-nav"> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,8 +19,9 @@ import { getResults } from '../core/indexUtils'; | |
* @example | ||
* import React from 'react'; | ||
* | ||
* import { connectHits, Highlight, InstantSearch } from 'react-instantsearch/dom'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was this a mistake previously? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. connectHits is from /connectors, so yes |
||
* | ||
* import { Highlight, InstantSearch } from 'react-instantsearch/dom'; | ||
* import { connectHits } from 'react-instantsearch/connectors'; | ||
* const CustomHits = connectHits(({ hits }) => | ||
* <div> | ||
* {hits.map(hit => | ||
|
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm having a little doubts about the name here. I think we can find something that describes intent rather than what it is.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be use in the future for future use case such as retrieving the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no API feature in this connector that has any conditional mean (no if/else) so naming it conditional would be not the best to me too. |
||
* ({ 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, | ||
}); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,46 @@ | ||
import React from 'react'; | ||
import { storiesOf } from '@storybook/react'; | ||
import { createConnector } from '../packages/react-instantsearch'; | ||
import { connectStateResults } from '../packages/react-instantsearch/connectors'; | ||
import { WrapWithHits } from './util'; | ||
|
||
const stories = storiesOf('Conditionals', module); | ||
|
||
stories | ||
.add('NoResults/HasResults', () => { | ||
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 has been found for {query}</div> | ||
) : ( | ||
<div>Some results</div> | ||
); | ||
return <div>{content}</div>; | ||
}); | ||
const Content = connectStateResults( | ||
({ searchState, searchResults }) => | ||
searchResults && searchResults.nbHits !== 0 ? ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. N.I.C.E There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks even nicer in future JS: searchResults?.nbHits !== 0 ? |
||
<div>Some results</div> | ||
) : ( | ||
<div>No results has been found for {searchState.query}</div> | ||
) | ||
); | ||
return ( | ||
<WrapWithHits linkedStoryGroup="Conditional"> | ||
<Content /> | ||
</WrapWithHits> | ||
); | ||
}) | ||
.add('NoQuery/HasQuery', () => { | ||
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> | ||
) | ||
); | ||
return ( | ||
<WrapWithHits linkedStoryGroup="Conditional"> | ||
<Content /> | ||
</WrapWithHits> | ||
); | ||
}) | ||
.add('NoLoading/HasQuery', () => { | ||
const Content = connectStateResults( | ||
({ searching }) => | ||
searching ? <div>searching</div> : <div>No searching</div> | ||
); | ||
return ( | ||
<WrapWithHits linkedStoryGroup="Conditional"> | ||
<Content /> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we can't call this:
({ state, results })
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's called
searchState
andsearchResults
everywhere especially in the documentation when looking for the structure so I'd rather keep the same name. If a user wants to call them state or results, they can definitely do it.