<Search>
This Enterprise Edition component, part of ra-search
, lets user do a site-wide search via a smart Omnibox.
<Search>
renders a global search input. It’s designed to be integrated into the top <AppBar>
.
It relies on the dataProvider
to provide a search()
method, so you can use it with any search engine (Lucene, ElasticSearch, Solr, Algolia, Google Cloud Search, and many others). And if you don’t have a search engine, no problem! <Search>
can also do the search across several resources via parallel dataProvider.getList()
queries.
Usage
Install ra-search
The <Search>
component is part of the @react-admin/ra-search
package. To install it, run:
yarn add '@react-admin/ra-search'
This requires a valid subscription to React-admin Enterprise Edition.
Implement dataProvider.search()
Your dataProvider
should support the search()
method. It should return a Promise for data
containing an array of SearchResult
objects and a total
. A SearchResult
contains at least the following fields:
id
: Identifier The unique identifier of the search resulttype
: An arbitrary string which enables groupingurl
: The URL where to redirect to on click. It could be a custom page and not a resource if you want tocontent
: Can contain any data that will be used to display the result. If used with the default<SearchResultItem>
component, it must contain at least anid
,label
, and adescription
.matches
: An optional object containing an extract of the data with matches. Can be anything that will be interpreted by a<SearchResultItem>
As for the total
, it can be greater than the number of returned results. This is useful e.g. to show that there are more results.
Here is an example
dataProvider.search("roll").then((response) => console.log(response));
// {
// data: [
// { id: 'a7535', type: 'artist', url: '/artists/7535', content: { label: 'The Rolling Stones', description: 'English rock band formed in London in 1962' } }
// { id: 'a5352', type: 'artist', url: '/artists/5352', content: { label: 'Sonny Rollins', description: 'American jazz tenor saxophonist' } }
// { id: 't7524', type: 'track', url: '/tracks/7524', content: { label: 'Like a Rolling Stone', year: 1965, recordCompany: 'Columbia', artistId: 345, albumId: 435456 } }
// { id: 't2386', type: 'track', url: '/tracks/2386', content: { label: "It's Only Rock 'N Roll (But I Like It)", year: 1974, artistId: 7535, albumId: 6325 } }
// { id: 'a6325', type: 'album', url: '/albums/6325', content: { label: "It's Only rock 'N Roll", year: 1974, artistId: 7535 }}
// ],
// total: 5
// }
It is your responsibility to add this search method to your dataProvider
so that react-admin can send queries to and read responses from the search engine.
If you don’t have a search engine, you can use the addSearchMethod
helper to add a dataProvider.search()
method that does a parallel dataProvider.getList()
query for each resource.
// in src/dataProvider.js
import simpleRestProvider from 'ra-data-simple-rest';
import { addSearchMethod } from '@react-admin/ra-search';
const baseDataProvider = simpleRestProvider('http://path.to.my.api/');
export const dataProvider = addSearchMethod(baseDataProvider, [
// search across these resources
'artists',
'tracks',
'albums',
]);
Check the ra-search
documentation to learn more about the input and output format of dataProvider.search()
, as well as the possibilities to customize the addSearchMethod
.
Option 1: With <Layout>
If you’re using the <Layout
component, include the <Search>
component inside a custom <AppBar>
component:
// in src/MyAppBar.jsx
import { AppBar, TitlePortal } from "react-admin";
import { Search } from "@react-admin/ra-search";
export const MyAppbar = () => (
<AppBar>
<TitlePortal />
<Search />
</AppBar>
);
Include that AppBar in a custom layout component:
// in src/MyLayout.jsx
import { Layout } from "react-admin";
import { MyAppbar } from "./MyAppBar";
export const MyLayout = ({ children }) => (
<Layout appBar={MyAppbar}>
{children}
</Layout>
);
Finally, include that custom layout in the <Admin>
.
// in src/Admin.ts
import { Admin } from "react-admin";
import { dataProvider } from "./dataProvider";
import { MyLayout } from "./MyLayout";
export const App = () => (
<Admin
dataProvider={dataProvider}
layout={MyLayout}
>
// ...
</Admin>
);
Option 2: With <ContainerLayout>
If you’re using the <ContainerLayout>
component, you can use the <Search>
component directly in the toolbar
prop:
// in src/MyLayout.jsx
import { ContainerLayout } from "@react-admin/ra-navigation";
import { Search } from "@react-admin/ra-search";
const MyLayout = ({ children }) => (
<ContainerLayout maxWidth="xl" toolbar={<Search />}>
{children}
</ContainerLayout>
);
Then, import that custom layout in the <Admin>
:
// in src/Admin.ts
import { Admin } from "react-admin";
import { dataProvider } from "./dataProvider";
import { MyLayout } from "./MyLayout";
export const App = () => (
<Admin
dataProvider={dataProvider}
layout={MyLayout}
>
// ...
</Admin>
);
Props
The <Search>
component accepts the following props:
Prop | Required | Type | Default | Description |
---|---|---|---|---|
children |
Optional | Element |
<SearchResultsPanel> |
A component that will display the results. |
color |
Optional | string |
light |
The color mode for the input, applying light or dark backgrounds. Accept either light or dark . |
historySize |
Optional | number |
5 | The number of past queries to keep in history. |
options |
Optional | Object |
- | An object containing options to apply to the search. |
queryOptions |
Optional | UseQuery Options |
- | react-query options for the search query |
wait |
Optional | number |
500 | The delay of debounce for the search to launch after typing in ms. |
Additional props are passed down to the Material UI <TextField>
component.
children
The <Search>
children allow you to customize the way results are displayed. The child component can grab the search result using the useSearchResult
hook.
import { Admin, AppBar, TitlePortal, Layout } from 'react-admin';
import { Search, useSearchResult } from '@react-admin/ra-search';
const CustomSearchResultsPanel = () => {
const { data, onClose } = useSearchResult();
return (
<ul>
{data.map(searchResult => (
<li key={searchResult.id}>{searchResult.content.label}</li>
))}
</ul>
);
};
const MyAppBar = () => (
<AppBar>
<TitlePortal />
<Search>
<CustomSearchResultsPanel />
</Search>
</AppBar>
);
const MyLayout = ({ children }) => (
<Layout appBar={MyAppBar}>
{children}
</Layout>
);
export const App = () => (
<Admin dataProvider={searchDataProvider} layout={MyLayout}>
// ...
</Admin>
);
color
If you need it, you can choose to render the light
or the dark
version of search input.
<Search color="dark" />
historySize
The number of previous user searches to keep in the popover. For example, if a user performs 10 searches and historySize
is set to 5, the popover will display the user’s last 5 queries.
<Search historySize={5} />
options
An object containing options to apply to the search:
targets
:string[]
: an array of the indices on which to perform the search. Defaults to an empty array.{any}
:{any}
: any custom option to pass to the search engine.
<Search options={{ foo: 'bar' }} />
queryOptions
<Search>
accepts a queryOptions
prop to pass options to the react-query client.
This can be useful e.g. to override the default side effects such as onSuccess
or onError
.
<Search queryOptions={{ onSuccess: data => console.log(data) }} />
wait
The number of milliseconds to wait before processing the search request, immediately after the user enters their last character.
<Search wait={200} />
Customizing The Result Items
By default, <Search>
displays the results in <SearchResultsPanel>
, which displays each results in a <SearchResultItem>
. So rendering <Search>
without children is equivalent to rendering:
const MySearch = () => (
<Search>
<SearchResultsPanel>
<SearchResultItem />
</SearchResultsPanel>
</Search>
);
<SearchResultItem>
renders the content.label
and content.description
for each result. You can customize what it renders by providing a function as the label
and the description
props. This function takes the search result as a parameter and must return a React element.
For instance:
import {
Search,
SearchResultsPanel,
SearchResultItem,
} from '@react-admin/ra-search';
const MySearch = () => (
<Search>
<SearchResultsPanel>
<SearchResultItem
label={record => (
<>
{record.type === 'artists' ? (
<PersonIcon />
) : (
<MusicIcon />
)}
<span>{record.content.label}</span>
</>
)}
/>
</SearchResultsPanel>
</Search>
);
You can also completely replace the search result item component:
import { Search, SearchResultsPanel } from '@react-admin/ra-search';
const MySearchResultItem = ({ data, onClose }) => (
<li key={data.id}>
<Link to={data.url} onClick={onClose}>
<strong>{data.content.label}</strong>
</Link>
<p>{data.content.description}</p>
</li>
);
const MySearch = () => (
<Search>
<SearchResultsPanel>
<MySearchResultItem />
</SearchResultsPanel>
</Search>
);
Customizing the Entire Search Results
Pass a custom React element as a child of <Search>
to customize the appearance of the search results. This can be useful e.g. to customize the results grouping, or to arrange search results differently.
ra-search
renders the <Search>
inside a SearchContext
. You can use the useSearchResultContext
hook to read the search results, as follows:
import { Search, useSearchResult } from '@react-admin/ra-search';
const MySearch = props => (
<Search>
<CustomSearchResultsPanel />
</Search>
);
const CustomSearchResultsPanel = () => {
const { data, onClose } = useSearchResult();
return (
<ul>
{data.map(searchResult => (
<li key={searchResult.id}>
<Link to={searchResult.url} onClick={onClose}>
<strong>{searchResult.content.label}</strong>
</Link>
<p>{searchResult.content.description}</p>
</li>
))}
</ul>
);
};