- Headless Core
- Component Reference
- Customization
- Advanced Configuration
- Build Your Own Component
- Connectors and Handlers
- Performance
- Debugging
|
@elastic/react-search-ui | @elastic/search-ui
|
|
SearchProvider <--------------- SearchDriver
| | | |
State / | | | | State /
Actions | | | | Actions
| | | |
Components | | |
| | | |
v v | v
------------------------------------+----------------------------
| | |
v v v
Using Headless Usage Headless Usage outside
Components in React of React
The core is a separate, vanilla JS library which can be used for any JavaScript based implementation.
The Headless Core implements the functionality behind a search experience, but without its own view. It provides the underlying "state" and "actions" associated with that view. For instance, the core provides a setSearchTerm
action, which can be used to save a searchTerm
property in the state. Calling setSearchTerm
using the value of an <input>
will save the searchTerm
to be used to build a query.
All of the Components in this library use the Headless Core under the hood. For instance, Search UI provides a SearchBox
Component for collecting input from a user. But you are not restricted to using just that Component. Since Search UI lets you work directly with "state" and "actions", you could use any type of input you want! As long as your input or Component calls the Headless Core's setSearchTerm
action, it will "just work". This gives you maximum flexibility over your experience if you need more than the Components in Search UI have to offer.
The SearchProvider
is a React wrapper around the Headless Core, and makes state and actions available to Search UI
and in a React Context, and also via a
Render Prop.
It looks like this:
<SearchProvider config={config}>
<WithSearch>
{/*WithSearch exposes the "Context"*/}
{context => {
// Context contains state, like "searchTerm"
const searchTerm = context.searchTerm;
// Context also contains actions, like "setSearchTerm"
const setSearchTerm = context.setSearchTerm;
return (
<div className="App">
{/*An out-of-the-box Component like SearchBox uses State and Actions under the hood*/}
<SearchBox />
{/*We could work directly with those State and Actions also */}
<input value={searchTerm} onChange={setSearchTerm} />
</div>
);
}}
</WithSearch>
</SearchProvider>
If you wish to work with Search UI outside of a particular Component, you'll work directly with the core.
There are two methods for accessing the headless core directly, withSearch
and
WithSearch
. They use the HOC and
Render Props patterns, respectively. The two methods
are similar, and choosing between the two is mostly personal preference.
Both methods expose a mapContextToProps
function which allows you to pick which state and actions
from context you need to work with.
mapContextToProps
allows you to pick which state and actions
from Context you need to work with. withSearch
and WithSearch
both use React.PureComponent,
and will only re-render when the picked state has changed.
name | type | description |
---|---|---|
context | Object | The current Context |
props | Object | The current props |
ex.:
// Selects `searchTerm` and `setSearchTerm` for use in Component
withSearch(({ searchTerm, setSearchTerm }) => ({
searchTerm,
setSearchTerm
}))(Component);
// Uses current `props` to conditionally modify context
withSearch(({ searchTerm }, { someProp }) => ({
searchTerm: someProp ? "" : searchTerm
}))(Component);
This is the HOC approach to working with the core.
This is typically used for creating your own Components.
This is the Render Props approach to working with the core.
One use case for that would be to render a "loading" indicator any time the application is fetching data.
For example:
<SearchProvider config={config}>
<WithSearch mapContextToProps={({ isLoading }) => ({ isLoading })}>
{({ isLoading }) => (
<div className="App">
{isLoading && <div>I'm loading now</div>}
{!isLoading && (
<Layout
header={<SearchBox />}
bodyContent={<Results titleField="title" urlField="nps_link" />}
/>
)}
</div>
)}
</WithSearch>
</SearchProvider>
There are certain cases where you may need to apply one or more actions at a time. Search UI intelligently batches actions into a single API call.
For example, if you need to apply two filters at once, it is perfectly acceptable to write the following code:
addFilter("states", "Alaska", "any");
addFilter("world_heritage_site", "true");
This will only result in a single API call.
The SearchProvider
is a top-level Component which is essentially a wrapper around the core.
It exposes the State and Actions of the core in a Context.
Params:
name | type | description |
---|---|---|
config | Object | See the Advanced Configuration section. |
children | React Node |
The "Context" is a flattened object containing, as keys, all State and Actions.
We refer to it as "Context" because it is implemented with a React Context.
ex.
{
resultsPerPage: 10, // Request State
setResultsPerPage: () => {}, // Action
current: 1, // Request State
setCurrent: () => {}, // Action
error: '', // Response State
isLoading: false, // Response State
totalResults: 1000, // Response State
...
}
method | params | return | description |
---|---|---|---|
addFilter |
name String - field name to filter onvalue FilterValue to applyfilterType String - type of filter to apply: "all", "any", or "none" |
Add a filter in addition to current filters values. | |
setFilter |
name String - field name to filter onvalue FilterValue to applyfilterType String - type of filter to apply: "all", "any", or "none" |
Set a filter value, replacing current filter values. | |
removeFilter |
name String - field to remove filters fromvalue String - (Optional) Specify which filter value to removefilterType String - (Optional) Specify which filter type to remove: "all", "any", or "none" |
Removes filters or filter values. | |
reset |
Reset state to initial search state. | ||
clearFilters |
except Array[String] - List of field names that should NOT be cleared |
Clear all filters. | |
setCurrent |
Integer | Update the current page number. Used for paging. | |
setResultsPerPage |
Integer | ||
setSearchTerm |
searchTerm Stringoptions Objectoptions.refresh Boolean - Refresh search results on update. Default: true .options.debounce Number - Length to debounce any resulting queries.options.shouldClearFilters Boolean - Should existing filters be cleared? Default: true .options.autocompleteSuggestions Boolean - Fetch query suggestions for autocomplete on update, stored in autocompletedSuggestions stateoptions.autocompleteResults Boolean - Fetch results on update, stored in autocompletedResults state |
||
setSort |
sortField String - field to sort onsortDirection String - "asc" or "desc" |
||
trackClickThrough |
documentId String - The document ID associated with the result that was clickedtag - Array[String] Optional tags which can be used to categorize this click event |
Report a clickthrough event, which is when a user clicks on a result link. | |
a11yNotify |
messageFunc String - object key to run as functionmessageArgs Object - Arguments to pass to form your screen reader message string |
Reads out a screen reader accessible notification. See a11yNotificationMessages under Advanced Configuration |
State can be divided up into a few different types.
- Request State - State that is used as parameters on Search API calls.
- Result State - State that represents a response from a Search API call.
- Application State - The general state.
Request State and Result State will often have similar values. For instance, searchTerm
and resultSearchTerm
.
searchTerm
is the current search term in the UI, and resultSearchTerm
is the term associated with the current
results
. This can be relevant in the UI, where you might not want the search term on the page to change until AFTER
a response is received, so you'd use the resultSearchTerm
state.
State that is used as parameters on Search API calls.
Request state can be set by:
- Using actions, like
setSearchTerm
- The
initialState
option. - The URL query string, if
trackUrlState
is enabled.
option | type | required? | source |
---|---|---|---|
current |
Integer | optional | Current page number |
filters |
Array[Filter] | optional | |
resultsPerPage |
Integer | optional | Number of results to show on each page |
searchTerm |
String | optional | Search terms to search for |
sortDirection |
String ["asc" | "desc"] | optional | Direction to sort |
sortField |
String | optional | Name of field to sort on |
State that represents a response from a Search API call.
It is not directly update-able.
It is updated indirectly by invoking an action which results in a new API request.
field | type | description |
---|---|---|
autocompletedResults |
Array[Result] | An array of results items fetched for an autocomplete dropdown. |
autocompletedResultsRequestId |
String | A unique ID for the current autocompleted search results. |
autocompletedSuggestions |
Object[String, Array[Suggestion] | A keyed object of query suggestions. It's keyed by type since multiple types of query suggestions can be set here. |
autocompletedSuggestionsRequestId |
String | A unique ID for the current autocompleted suggestion results. |
facets |
Object[Facet] | Will be populated if facets configured in Advanced Configuration. |
rawResponse |
Object | The response object received from the API |
requestId |
String | A unique ID for the current search results. |
results |
Array[Result] | An array of result items. |
resultSearchTerm |
String | As opposed the the searchTerm state, which is tied to the current search parameter, this is tied to the searchTerm for the current results. There will be a period of time in between when a request is started and finishes where the two pieces of state will differ. |
totalResults |
Integer | Total number of results found for the current query. |
Application state is the general application state.
field | type | description |
---|---|---|
error |
String | Error message, if an error was thrown. |
isLoading |
boolean | Whether or not a search is currently being performed. |
wasSearched |
boolean | Has any query been performed since this driver was created? Can be useful for displaying initial states in the UI. |
Note that all components in this library are Pure Components. Read more about that here.
The following Components are available:
Input element which accepts search terms and triggers a new search query.
import { SearchBox } from "@elastic/react-search-ui";
...
<SearchBox />
The input from SearchBox
will be used to trigger a new search query. That query can be further customized
in the SearchProvider
configuration, using the searchQuery
property. See the
Advanced Configuration guide for more information.
<SearchBox inputProps={{ placeholder: "custom placeholder" }} />
You can customize the entire view. This is useful to use an entirely different autocomplete library (we use downshift). But for making small customizations, like simply hiding the search button, this is often overkill.
<SearchBox
view={({ onChange, value, onSubmit }) => (
<form onSubmit={onSubmit}>
<input value={value} onChange={e => onChange(e.currentTarget.value)} />
</form>
)}
/>
You can also just customize the input section of the search box. Useful for things like hiding the submit button or rearranging dom structure:
<SearchBox
inputView={({ getAutocomplete, getInputProps, getButtonProps }) => (
<>
<div className="sui-search-box__wrapper">
<input
{...getInputProps({
placeholder: "I am a custom placeholder"
})}
/>
{getAutocomplete()}
</div>
<input
{...getButtonProps({
"data-custom-attr": "some value"
})}
/>
</>
)}
/>
Note that getInputProps
and getButtonProps
are
prop getters.
They are meant return a props object to spread over their corresponding UI elements. This lets you arrange
elements however you'd like in the DOM. It also lets you pass additional properties. You should pass properties
through these functions, rather directly on elements, in order to not override base values. For instance,
adding a className
through these functions will assure that the className is only appended, not overriding base class values on those values.
getAutocomplete
is used to determine where the autocomplete dropdown will be shown.
Or you can also just customize the autocomplete dropdown:
<SearchBox
autocompleteView={({ autocompletedResults, getItemProps }) => (
<div className="sui-search-box__autocomplete-container">
{autocompletedResults.map((result, i) => (
<div
{...getItemProps({
key: result.id.raw,
item: result
})}
>
Result {i}: {result.title.snippet}
</div>
))}
</div>
)}
/>
"Results" are search results. The default behavior for autocomplete results is to link the user directly to a result when selected, which is why a "titleField" and "urlField" are required for the default view.
<SearchBox
autocompleteResults={{
titleField: "title",
urlField: "nps_link"
}}
/>
"Suggestions" are different than "results". Suggestions are suggested queries. Unlike an autocomplete result, a suggestion does not go straight to a result page when selected. It acts as a regular search query and refreshes the result set.
<SearchBox autocompleteSuggestions={true} />
The default view will show both results and suggestions, divided into sections. Section titles can be added to help distinguish between the two.
<SearchBox
autocompleteResults={{
sectionTitle: "Suggested Results",
titleField: "title",
urlField: "nps_link"
}}
autocompleteSuggestions={{
sectionTitle: "Suggested Queries"
}}
/>
Autocomplete queries can be customized in the SearchProvider
configuration, using the autocompleteQuery
property.
See the Advanced Configuration Guide for more information.
<SearchProvider
config={{
...
autocompleteQuery: {
// Customize the query for autocompleteResults
results: {
result_fields: {
// Add snippet highlighting
title: { snippet: { size: 100, fallback: true } },
nps_link: { raw: {} }
}
},
// Customize the query for autocompleteSuggestions
suggestions: {
types: {
// Limit query to only suggest based on "title" field
documents: { fields: ["title"] }
},
// Limit the number of suggestions returned from the server
size: 4
}
}
}}
>
<SearchBox
autocompleteResults={{
sectionTitle: "Suggested Results",
titleField: "title",
urlField: "nps_link"
}}
autocompleteSuggestions={{
sectionTitle: "Suggested Queries",
}}
/>
</SearchProvider>
"Suggestions" can be generated via multiple methods. They can be derived from common terms and phrases inside of documents, or be "popular" queries generated from actual search queries made by users. This will differ depending on the particular Search API you are using.
Note: Elastic App Search currently only supports type "documents", and Elastic Site Search does not support suggestions. This is purely illustrative in case a Connector is used that does support multiple types.
<SearchProvider
config={{
...
autocompleteQuery: {
suggestions: {
types: {
documents: { },
// FYI, this is not a supported suggestion type in any current connector, it's an example only
popular_queries: { }
}
}
}
}}
>
<SearchBox
autocompleteSuggestions={{
// Types used here need to match types requested from the server
documents: {
sectionTitle: "Suggested Queries",
},
popular_queries: {
sectionTitle: "Popular Queries"
}
}}
/>
</SearchProvider>
This is an example from a Gatsby site, which overrides "submit" to navigate a user to the search page for suggestions, and maintaining the default behavior when selecting a result.
<SearchBox
autocompleteResults={{
titleField: "title",
urlField: "nps_link"
}}
autocompleteSuggestions={true}
onSubmit={searchTerm => {
navigate("/search?q=" + searchTerm);
}}
onSelectAutocomplete={(selection, {}, defaultOnSelectAutocomplete) => {
if (selection.suggestion) {
navigate("/search?q=" + selection.suggestion);
} else {
defaultOnSelectAutocomplete(selection);
}
}}
/>
Name | type | Required? | Default | Options | Description |
---|---|---|---|---|---|
className | String | no | |||
shouldClearFilters | Boolean | no | true | Should existing filters be cleared when a new search is performed? | |
inputProps | Object | no | Props for underlying 'input' element. I.e., { placeholder: "Enter Text"} . |
||
searchAsYouType | Boolean | no | false | Executes a new search query with every key stroke. You can fine tune the number of queries made by adjusting the debounceLength parameter. |
|
debounceLength | Number | no | 200 | When using searchAsYouType , it can be useful to "debounce" search requests to avoid creating an excessive number of requests. This controls the length to debounce / wait. |
|
view | Render Function | no | SearchBox | Used to override the default view for this Component. See the Customization: Component views and HTML section for more information. | |
autocompleteResults | Boolean or AutocompleteResultsOptions | Object | no | Configure and autocomplete search results. Boolean option is primarily available for implementing custom views. | |
autocompleteSuggestions | Boolean or AutocompleteSuggestionsOptions | Object | no | Configure and autocomplete query suggestions. Boolean option is primarily available for implementing custom views. Configuration may or may not be keyed by "Suggestion Type", as APIs for suggestions may support may than 1 type of suggestion. If it is not keyed by Suggestion Type, then the configuration will be applied to the first type available. | |
autocompleteMinimumCharacters | Integer | no | 0 | Minimum number of characters before autocompleting. | |
autocompleteView | Render Function | no | Autocomplete | Provide a different view just for the autocomplete dropdown. | |
inputView | Render Function | no | SearchInput | Provide a different view just for the input section. | |
onSelectAutocomplete | Function(selection. options, defaultOnSelectAutocomplete) | no | Allows overriding behavior when selected, to avoid creating an entirely new view. In addition to the current selection , various helpers are passed as options to the second parameter. This third parameter is the default onSelectAutocomplete , which allows you to defer to the original behavior. |
||
onSubmit | Function(searchTerm) | no | Allows overriding behavior when submitted. Receives the search term from the search box. |
Name | type | Required? | Default | Options | Description |
---|---|---|---|---|---|
linkTarget | String | no | _self | Used to open links in a new tab. | |
sectionTitle | String | no | Title to show in section within dropdown. | ||
shouldTrackClickThrough | Boolean | no | true | Only applies to Results, not Suggestions. | |
clickThroughTags | Array[String] | no | Tags to send to analytics API when tracking clickthrough. | ||
titleField | String | yes | Field within a Result to use as the "title". | ||
urlField | String | yes | Field within a Result to use for linking. |
Name | type | Required? | Default | Options | Description |
---|---|---|---|---|---|
sectionTitle | String | no | Title to show in section within dropdown |
Displays all search results.
import { Results } from "@elastic/react-search-ui";
...
<Results titleField="title" urlField="nps_link" />
Certain aspects of search results can be configured in SearchProvider
, using the searchQuery
configuration, such as
term highlighting and search fields. See the Advanced Configuration guide
for more information.
Name | type | Required? | Default | Options | Description |
---|---|---|---|---|---|
className | String | no | |||
resultView | Render Function | no | Result | Used to override individual Result views. See the Customizing Component views and html section for more information. | |
titleField | String | no | Name of field to use as the title from each result. | ||
shouldTrackClickThrough | Boolean | no | true | Whether or not to track a clickthrough event when clicked. | |
clickThroughTags | Array[String] | no | Tags to send to analytics API when tracking clickthrough. | ||
urlField | String | no | Name of field to use as the href from each result. | ||
view | Render Function | no | Results | Used to override the default view for this Component. See Customization: Component views and HTML for more information. |
Displays a search result.
import { Result } from "@elastic/react-search-ui";
...
<SearchProvider config={config}>
{({ results }) => {
return (
<div>
{results.map(result => (
<Result key={result.id.raw}
result={result}
titleField="title"
urlField="nps_link"
/>
))}
</div>
);
}}
</SearchProvider>
Certain aspects of search results can be configured in SearchProvider
, using the searchQuery
configuration, such as
term highlighting and search fields. See the Advanced Configuration guide
for more information.
Name | type | Required? | Default | Options | Description |
---|---|---|---|---|---|
className | String | no | |||
titleField | String | no | Name of field to use as the title from each result. | ||
shouldTrackClickThrough | Boolean | no | true | Whether or not to track a clickthrough event when clicked. | |
clickThroughTags | Array[String] | no | Tags to send to analytics API when tracking clickthrough. | ||
urlField | String | no | Name of field to use as the href from each result. | ||
view | Render Function | no | Result | Used to override the default view for this Component. See Customization: Component views and HTML for more information. | |
result | Result | no | Used to override the default view for this Component. See Customization: Component views and HTML for more information. |
Shows a dropdown for selecting the number of results to show per page.
Uses [20, 40, 60] as default options. You can use options
prop to pass custom options.
Note: When passing custom options make sure one of the option values match
the current resultsPerPage
value, which is 20 by default.
To override resultsPerPage
default value refer to the custom options example.
import { ResultsPerPage } from "@elastic/react-search-ui";
...
<ResultsPerPage />
import { SearchProvider, ResultsPerPage } from "@elastic/react-search-ui";
<SearchProvider
config={
...
initialState: {
resultsPerPage: 5
}
}
>
<ResultsPerPage options={[5, 10, 15]} />
</SearchProvider>
Name | type | Required? | Default | Options | Description |
---|---|---|---|---|---|
className | String | no | |||
options | Array[Number] | no | [20, 40, 60] | Dropdown options to select the number of results to show per page. | |
view | Render Function | no | ResultsPerPage | Used to override the default view for this Component. See Customization: Component views and HTML for more information. |
Show a Facet filter for a particular field.
Must configure the corresponding field in the SearchProvider
facets object.
import { Facet } from "@elastic/react-search-ui";
import { MultiCheckboxFacet } from "@elastic/react-search-ui-views";
...
<SearchProvider config={{
...otherConfig,
searchQuery: {
facets: {
states: { type: "value", size: 30 }
}
}
}}>
{() => (
<Facet field="states" label="States" view={MultiCheckboxFacet} />
)}
</SearchProvider>
Certain configuration of the Facet
Component will require a "disjunctive" facet to work
correctly. "Disjunctive" facets are facets that do not change when a selection is made. Meaning, all available options
will remain as selectable options even after a selection is made.
import { Facet } from "@elastic/react-search-ui";
import { MultiCheckboxFacet } from "@elastic/react-search-ui-views";
...
<SearchProvider config={{
...otherConfig,
searchQuery: {
disjunctiveFacets: ["states"],
facets: {
states: { type: "value", size: 30 }
}
}
}}>
{() => (
<Facet field="states" label="States" view={MultiCheckboxFacet} filterType="any" />
)}
</SearchProvider>
Name | type | Required? | Default | Options | Description |
---|---|---|---|---|---|
className | String | no | |||
field | String | yes | Field name corresponding to this filter. This requires that the corresponding field has been configured in facets on the top level Provider. |
||
filterType | String | no | "all" | "all", "any", "none" | The type of filter to apply with the selected values. I.e., should "all" of the values match, or just "any" of the values, or "none" of the values. Note: See the example above which describes using "disjunctive" facets in conjunction with filterType. |
label | String | yes | A static label to show in the facet filter. | ||
show | Number | no | 5 | The number of facet filter options to show before concatenating with a "more" link. | |
view | Render Function | no | MultiCheckboxFacet | SingleLinksFacet SingleSelectFacet BooleanFacet |
Used to override the default view for this Component. See Customization: Component views and HTML for more information. |
isFilterable | Boolean | no | false | Whether or not to show Facet quick filter. |
Shows a dropdown for selecting the current Sort.
import { Sorting } from "@elastic/react-search-ui";
...
<Sorting
sortOptions={[
{
name: "Relevance",
value: "",
direction: ""
},
{
name: "Title",
value: "title",
direction: "asc"
}
]}
/>
Name | type | Required? | Default | Options | Description |
---|---|---|---|---|---|
className | String | no | |||
label | Array[SortOption] | no | A static label to show in the Sorting Component. | ||
sortOptions | Array[SortOption] | yes | |||
view | Render Function | no | Sorting | Used to override the default view for this Component. See Customization: Component views and HTML for more information. |
Navigate through pagination.
import { Paging } from "@elastic/react-search-ui";
...
<Paging />
Name | type | Required? | Default | Options | Description |
---|---|---|---|---|---|
className | String | no | |||
view | Render Function | no | Paging | Used to override the default view for this Component. See Customization: Component views and HTML for more information. |
Paging details, like "1 - 20 of 100 results".
import { PagingInfo } from "@elastic/react-search-ui";
...
<PagingInfo />
Name | type | Required? | Default | Options | Description |
---|---|---|---|---|---|
className | String | no | |||
view | Render Function | no | PagingInfo | Used to override the default view for this Component. See Customization: Component views and HTML for more information. |
Handle unexpected errors.
import { ErrorBoundary } from "@elastic/react-search-ui";
...
<ErrorBoundary>
<div>Some Content</div>
</ErrorBoundary>
Name | type | Required? | Default | Options | Description |
---|---|---|---|---|---|
className | String | no | |||
children | React node | yes | Content to show if no error has occurred, will be replaced with error messaging if there was an error. | ||
view | Render Function | no | ErrorBoundary | Used to override the default view for this Component. See Customization: Component views and HTML for more information. |
Styling is up to you.
You can choose use the out of the box styles, or customize them.
To provide custom styles:
- Write your own styles that target the class names in the individual Components. Do NOT include
styles.css
. - Override the default styles. Include
styles.css
, and then overwrite with your own styles.
For layout, provide your own layout instead of using the Layout
Component.
For views and HTML, see the next section.
All Components in this library can be customized by providing a view
prop.
Each Component's view
will have a custom signature.
This follows the React Render Props pattern.
The clearest way to determine a Component's view
function signature is to
look at the corresponding view Component's source code in
react-search-ui-views. Each Component in that
library implements a view
function for a Component in the React library, so it
serves as a great reference.
For example, if we were to customize the PagingInfo
Component...
We'd look up the default view from the Components Reference section for the PagingInfo
Component.
The corresponding view is PagingInfo -- see how the naming matches up?
After viewing that Component's source, you'll see it accepts 4 props:
end
searchTerm
start
totalResults
In our case, we care about the start
and end
values.
We provide a view function that uses those two props:
<PagingInfo
view={({ start, end }) => (
<div className="paging-info">
<strong>
{start} - {end}
</strong>
</div>
)}
/>
We could also accomplish this with a functional Component:
const PagingInfoView = ({ start, end }) => (
<div className="paging-info">
<strong>
{start} - {end}
</strong>
</div>
);
return <PagingInfo view={PagingInfoView} />;
It will be helpful to read the Headless Core section first.
We have two primary recommendations for customizing Component behavior:
- Override state and action props before they are passed to your Component, using the
mapContextToProps
param. This will override the default mapContextToProps for the component. - Override props before they are passed to your Component's view.
Every Component supports a mapContextToProps
prop, which allows you to modify state and actions
before they are received by the Component.
NOTE This MUST be an immutable function. If you directly update the props or context, you will have major issues in your application.
A practical example might be putting a custom sort on your facet data.
This example orders a list of states by name:
<Facet
mapContextToProps={context => {
if (!context.facets.states) return context;
return {
...context,
facets: {
...(context.facets || {}),
states: context.facets.states.map(s => ({
...s,
data: s.data.sort((a, b) => {
if (a.value > b.value) return 1;
if (a.value < b.value) return -1;
return 0;
})
}))
}
};
}}
field="states"
label="States"
show={10}
/>
An example of this is modifying the onChange
handler of the Paging
Component
view. Hypothetically, you may need to know every time a user
pages past page 1, indicating that they are not finding what they need on the first page
of search results.
import { Paging } from "@elastic/react-search-ui";
import { Paging as PagingView } from "@elastic/react-search-ui-views";
function reportChange(value) {
// Some logic to report the change
}
<Paging
view={props =>
PagingView({
...props,
onChange: value => {
reportChange(value);
return props.onChange(value);
}
})
}
/>;
In this example, we did the following:
- Looked up what the default view is for our Component in the Component Reference guide.
- Imported that view as
PagingView
. - Passed an explicit
view
to ourPaging
Component, overriding theonChange
prop with our own implementation, and ultimately renderingPagingView
with the updated props.
All configuration for Search UI is provided in a single configuration object.
const configurationOptions = {
apiConnector: connector,
searchQuery: {
disjunctiveFacets: ["acres"],
disjunctiveFacetsAnalyticsTags: ["Ignore"],
search_fields: {
title: {},
description: {}
},
result_fields: {
title: {
snippet: {
size: 100,
fallback: true
}
},
nps_link: {
raw: {}
},
description: {
snippet: {
size: 100,
fallback: true
}
}
},
facets: {
states: { type: "value", size: 30 },
acres: {
type: "range",
ranges: [
{ from: -1, name: "Any" },
{ from: 0, to: 1000, name: "Small" },
{ from: 1001, to: 100000, name: "Medium" },
{ from: 100001, name: "Large" }
]
}
}
},
hasA11yNotifications: true,
a11yNotificationMessages: {
searchResults: ({ start, end, totalResults, searchTerm }) =>
`Searching for "${searchTerm}". Showing ${start} to ${end} results out of ${totalResults}.`
},
alwaysSearchOnInitialLoad: true
};
return (
<SearchProvider config={configurationOptions}>
{() => (
<div className="App">
<Layout
header={<SearchBox />}
bodyContent={<Results titleField="title" urlField="nps_link" />}
/>
</div>
)}
</SearchProvider>
);
It is helpful to read the section on the headless core first!
option | type | required? | default | description |
---|---|---|---|---|
apiConnector |
APIConnector | optional | Instance of a Connector. For instance, search-ui-app-search-connector. | |
onSearch |
function | optional | You may provide individual handlers instead of a Connector, override individual Connector handlers, or act as middleware to Connector methods. See Connectors and Handlers for more information. | |
onAutocomplete |
function | optional | You may provide individual handlers instead of a Connector, override individual Connector handlers, or act as middleware to Connector methods. See Connectors and Handlers for more information. | |
onResultClick |
function | optional | You may provide individual handlers instead of a Connector, override individual Connector handlers, or act as middleware to Connector methods. See Connectors and Handlers for more information. | |
onAutocompleteResultClick |
function | optional | You may provide individual handlers instead of a Connector, override individual Connector handlers, or act as middleware to Connector methods. See Connectors and Handlers for more information. | |
autocompleteQuery |
Object | optional | {} | Configuration options for the main search query. |
- results - Query Config |
Configuration options for results query, used by autocomplete. | |||
- suggestions - Suggestions Query Config |
Configuration options for suggestions query, used by autocomplete. | |||
debug |
Boolean | optional | false | Trace log actions and state changes. |
initialState |
Object | optional | Set initial State of the search. Any Request State can be set here. This is useful for defaulting a search term, sort, etc. Example: { searchTerm: "test", resultsPerPage: 40 } |
|
searchQuery |
Query Config | optional | {} | Configuration options for the main search query. |
trackUrlState |
Boolean | optional | true | By default, Request State will be synced with the browser url. To turn this off, pass false . |
urlPushDebounceLength |
Integer | optional | 500 | The amount of time in milliseconds to debounce/delay updating the browser url after the UI update. This, for example, prevents excessive history entries while a user is still typing in a live search box. |
hasA11yNotifications |
Boolean | optional | false | Search UI will create a visually hidden live region to announce search results & other actions to screen reader users. This accessibility feature will be turned on by default in our 2.0 release. |
a11yNotificationMessages |
Object | optional | {} | You can override our default screen reader messages (e.g. for localization), or create your own custom notification, by passing in your own key and message function(s). |
alwaysSearchOnInitialLoad |
Boolean | optional | false | If true, Search UI will always do an initial search, even when no inital Request State is set. |
Query configuration for Search UI largely follows the same API as the App Search Search API.
For example, if you add a search_fields
configuration option, it will control which fields are actually returned from the API.
option | type | required? | default | description |
---|---|---|---|---|
facets |
Object | optional | App Search Facets API Reference. Tells Search UI to fetch facet data that can be used to build Facet Components. Example, using states field for faceting:facets: {states: { type: "value", size: 30 } |
|
disjunctiveFacets |
Array[String] | optional | An array of field names. Every field listed here must have been configured in the facets field first. It denotes that a facet should be considered disjunctive. When returning counts for disjunctive facets, the counts will be returned as if no filter is applied on this field, even if one is applied. Example, specifying states field as disjunctive:disjunctiveFacets: ['states'] |
|
disjunctiveFacetsAnalyticsTags |
Array[String] | optional | Used in conjunction with the disjunctiveFacets parameter. Adding disjunctiveFacets can cause additional API requests to be made to your API, which can create deceiving analytics. These queries will be tagged with "Facet-Only" by default. This field lets you specify a different tag for these. Example, use junk as a tag on all disjunctive API calls:disjunctiveFacetsAnalyticsTags: ['junk'] |
|
conditionalFacets |
Object[String, function] | optional | This facet will only be fetched if the condition specified returns true , based on the currently applied filters. This is useful for creating hierarchical facets.Example: don't return states facet data unless parks is a selected filter.{ states: filters => isParkSelected(filters) } |
|
search_fields |
Object[String, Object] | optional | Fields which should be searched with search term. App Search search_fields API Reference |
|
result_fields |
Object[String, Object] | optional | Fields which should be returned in results. App Search result_fields API Reference |
|
* Request State | optional | Any request state value can be provided here. If provided, it will ALWAYS override the value from state. |
Using Query Config, it is possible to create "Global" filters. "Global filters" are filters that are added to every query. The user has no control over whether or not this filter is added or removed, it doesn't show up in the query string, and is completely transparent to the user. It is applied IN ADDITION to filters which the user applies.
<SearchProvider config={{
searchQuery: {
filters: [{ field: "world_heritage_site", values: ["true"] }],
}}>
</SearchProvider>
Suggestions Query configuration for Search UI largely follows the same API as the App Search Search API.
Ex.
{
"types": {
"documents": {
"fields": [
"title",
"states"
]
}
},
"size": 4
}
option | type | required? | source |
---|---|---|---|
types |
Object | required | Object, keyed by "type" of query suggestion, with configuration for that type of suggestion. |
size |
Integer | optional | Number of suggestions to return. |
Search UI makes all of the search API calls for your application.
You can control what these API calls look like with options such as search_fields
, result_fields
, and facets
.
But there may be cases where certain API operations are not supported by Search UI.
For example, App Search supports a "grouping" feature, which Search UI does not support out of the box.
We can work around that by using the beforeSearchCall
hook on the App Search Connector. This acts as a middleware
that gives you an opportunity to modify API requests and responses before they are made.
const connector = new AppSearchAPIConnector({
searchKey: "search-371auk61r2bwqtdzocdgutmg",
engineName: "search-ui-examples",
hostIdentifier: "host-2376rb",
beforeSearchCall: (existingSearchOptions, next) =>
next({
...existingSearchOptions,
group: { field: "title" }
})
});
Learn about the Headless Core concepts first!
We provide a variety of Components out of the box.
There might be cases where we do not have the Component you need.
In this case, we provide a Higher Order Component called withSearch.
It gives you access to work directly with Search UI's Headless Core.
This lets you create your own Components for Search UI.
Ex. Creating a Component for clearing all filters
import React from "react";
import { withSearch } from "@elastic/react-search-ui";
function ClearFilters({ filters, clearFilters }) {
return (
<div>
<button onClick={() => clearFilters()}>
Clear {filters.length} Filters
</button>
</div>
);
}
export default withSearch(({ filters, clearFilters }) => ({
filters,
clearFilters
}))(ClearFilters);
Note that withSearch
accepts a mapContextToProps
function as the first parameter. Read more about that
in the mapContextToProps section.
Also note that all components created with withSearch
will be Pure Components. Read more
about that here.
Learn about the Headless Core concepts first!
Search UI exposes a number of event hooks which need handlers to be implemented in order for Search UI to function properly.
The easiest way to provide handlers for these events is via an out-of-the-box "Connector", which provides pre-built handlers, which can then be configured for your particular use case.
While we do provide out-of-the-box Connectors, it is also possible to implement these handlers directly, override Connector methods, or provide "middleware" to Connectors in order to further customize how Search UI interacts with your services.
method | params | return | description |
---|---|---|---|
onResultClick |
props - Object |
This method logs a click-through event to your APIs analytics service. This is triggered when a user clicks on a result on a result page. | |
- query - String |
The query used to generate the current results. | ||
- documentId - String |
The id of the result that a user clicked. | ||
- requestId - String |
A unique id that ties the click to a particular search request. | ||
- tags - Array[String] |
Tags used for analytics. | ||
onSearch |
state - Request State |
Response State | |
queryConfig - Query Config |
|||
onAutocompleteResultClick |
props - Object |
This method logs a click-through event to your APIs analytics service. This is triggered when a user clicks on a result in an autocomplete dropdown | |
- query - String |
The query used to generate the current results. | ||
- documentId - String |
The id of the result that a user clicked. | ||
- requestId - String |
A unique id that ties the click to a particular search request. | ||
- tags - Array[String] |
Tags used for analytics. | ||
onAutocomplete |
state - Request State |
Response State | |
queryConfig - Object |
|||
- results - Query Config |
If this is set, results should be returned for autocomplete. | ||
- suggestions - Suggestions Query Config |
If this is set, query suggestions should be returned for autocomplete. |
If you are using an API for search that there is no Connector for, it is possible to simply provide
handler implementations directly on the SearchProvider
.
<SearchProvider
config={{
onSearch: async state => {
const queryForOtherService = transformSearchUIStateToQuery(state);
const otherServiceResponse = await callSomeOtherService(
queryForOtherService
);
return transformOtherServiceResponseToSearchUIState(otherServiceResponse);
}
}}
/>
This makes Search UI useful for services like elasticsearch
which do not have a Connector
available.
For a thorough example of this, see the demo in examples/elasticsearch
Explicitly providing a Handler will override the Handler provided by the Connector.
<SearchProvider
config={{
apiConnector: connector,
onSearch: async (state, queryConfig) => {
const queryForOtherService = transformSearchUIStateToQuery(
state,
queryConfig
);
const otherServiceResponse = await callSomeOtherService(
queryForOtherService
);
return transformOtherServiceResponseToSearchUIState(otherServiceResponse);
}
}}
/>
Handler implementations can also be used as middleware for Connectors by leveraging
the next
function.
<SearchProvider
config={{
apiConnector: connector,
onSearch: (state, queryConfig, next) => {
const updatedState = someStateTransformation(state);
return next(updatedState, queryConfig);
}
}}
/>
An example of a connector is the Site Search API Connector.
A connector simply needs to implement the Event Handlers listed above. The handlers typically:
- Convert the current Request State and Query Config into the search semantics of your particular Search API.
- Convert the response from your particular Search API into Response State.
While some handlers are meant for fetching data and performing searches, other handlers are meant for recording
certain user events in analytics services, such as onResultClick
or onAutocompleteResultClick
.
For error handling, a method must throw any error with a "message" field populated for any unrecoverable error. This includes things like 404s, 500s, etc.
This library is optimized to avoid full sub-tree re-rendering, and so that components only re-render when state changes that are relevant to those particular components occur.
In order to accomplish this, all components within this library are "Pure Components". You can read more about the concept and potential pitfalls of Pure Components in the React Optimizing Performance guide.
The thing to be most cautious of is not to mutate state that you will pass as props to any of these components.
Example of what not to do:
class SomeComponent extends React.Component {
changeSorting = () => {
const { options } = this.state;
// Mutating an existing array in state rather than creating a new one is bad. Since Sorting component is "Pure"
// it won't update after calling `setState` here.
options.push("newOption");
this.setState({ options });
};
render() {
const { options } = this.state;
return <Sorting options={options} />;
}
}
Instead, do:
// Create a new options array and copy the old values into that new array.
this.setState(prevState => ({ options: [...prevState.options, "newOption"] }));
If you ever need to debug performance related issues, see the instructions in the Optimizing Performance guide for enabling the "Highlight Updates" feature in the React Developer tools for Chrome.
There is a debug
flag available on the configuration for SearchDriver
and SearchProvider
.
<SearchProvider config={
debug: true
//...
}>
Setting this to true
will make the searchUI
object available globally on window. This will allow you to
programmatically execute actions in the browser console which can be helpful for debugging.
window.searchUI.addFilter("states", "California", "all");
This will also log actions and state updates as they occur to the console in the following form:
Search UI: Action {Action Name} {Action Parameters}
Search UI: State Update {State to update} {Full State after update}