<SearchWithResult>
This Enterprise Edition component, part of 
ra-search, renders a search input and the search results directly below the input. Itβs ideal for dashboards or menu panels.
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! <SearchWithResult> can also do the search across several resources via parallel dataProvider.getList() queries.
By default, <SearchWithResult> will group the search results by target, and show their content.label and content.description.
Usage
Install ra-search
The <SearchWithResult> 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 result
- type: An arbitrary string which enables grouping
- url: The URL where to redirect to on click. It could be a custom page and not a resource if you want to
- content: Can contain any data that will be used to display the result. If used with the default- <SearchResultItem>component, it must contain at least an- id,- label, and a- description.
- 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',
]);
Then, hereβs how to include the <SearchWithResult> component inside a custom <Dashboard> component:
import { Card, CardContent } from '@mui/material';
import { Admin } from 'react-admin';
import { SearchWithResult } from '@react-admin/ra-search';
import { searchDataProvider } from './searchDataProvider';
const MyDashboard = () => (
    <Card>
        <CardContent>
            <SearchWithResult />
        </CardContent>
    </Card>
);
export const App = () => (
    <Admin dataProvider={searchDataProvider} dashboard={MyDashboard}>
        {/*...*/}
    </Admin>
);
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.
Props
| Prop | Required | Type | Default | Description | 
|---|---|---|---|---|
| children | Optional | Element | <SearchResultsPanel> | A component that will display the results. | 
| disableHighlight | Optional | boolean | false | Disable the highlight of the search term of each result. | 
| onNavigate | Optional | function | () => undefined | A callback function to run when the user navigate to a result. | 
| options | Optional | Object | - | An object containing options to apply to the search. | 
| queryOptions | Optional | UseQuery Options | - | react-queryoptions for the search query | 
| wait | Optional | number | 500 | The delay of debounce for the search to launch after typing in ms. | 
children
The <SearchWithResult> children allow you to customize the way results are displayed. The child component can grab the search result using the useSearchResult hook.
import { Admin } from 'react-admin';
import { SearchWithResult, useSearchResults } from '@react-admin/ra-search';
import { searchDataProvider } from './searchDataProvider';
const MyDashboard = () => (
    <SearchWithResult>
        <MySearchResultsPanel />
    </SearchWithResult>
);
const MySearchResultsPanel = () => {
    const { data } = useSearchResults();
    return (
        <ul>
            {data.map(item => (
                <li key={item.id}>{item.content.label}</li>
            ))}
        </ul>
    );
};
export const App = () => (
    <Admin dataProvider={searchDataProvider} dashboard={MyDashboard}>
        {/*...*/}
    </Admin>
);
disableHighlight
The search terms in each result are highlighted. You can disable this feature with the disableHighlight prop as follows:
<SearchWithResults disableHighlight />
Tip: To customize the highlight style check out the Customizing the result items section below.
onNavigate
onNavigate allows you to perform an action when the user clicks on a search result, e.g. to close a menu (See below for an example with <SolarLayout>).
import { Admin } from 'react-admin';
import { SearchWithResult } from '@react-admin/ra-search';
import { searchDataProvider } from './searchDataProvider';
const MyDashboard = () => {
    const handleNavigate = () => {
        console.log('User navigated to a result');
    };
    return <SearchWithResult onNavigate={handleNavigate} />;
};
export const App = () => (
    <Admin dataProvider={searchDataProvider} dashboard={MyDashboard}>
        {/*...*/}
    </Admin>
);
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.
<SearchWithResult options={{ foo: 'bar' }} />
queryOptions
<SearchWithResult> 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.
<SearchWithResult 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.
<SearchWithResult wait={200} />
Customizing the Entire Search Results
Pass a custom React element as a child of <SearchWithResult> 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 <SearchResultsPanel> inside a SearchContext. You can use the useSearchResult hook to read the search results, as follows:
import { Card, CardContent } from '@mui/material';
import { Link } from 'react-router-dom';
import { Admin } from 'react-admin';
import {
    SearchWithResult,
    SearchResultsPanel,
    useSearchResults,
} from '@react-admin/ra-search';
import { searchDataProvider } from './searchDataProvider';
const MyDashboard = () => (
    <Card>
        <CardContent>
            <SearchWithResult>
                <MySearchResultsPanel />
            </SearchWithResult>
        </CardContent>
    </Card>
);
const MySearchResultsPanel = () => {
    const { data } = useSearchResults();
    return (
        <ul style={{ maxHeight: '250px', overflow: 'auto' }}>
            {data.map(item => (
                <li key={item.id}>
                    <Link to={item.url}>
                        <strong>{item.content.label}</strong>
                    </Link>
                    <p>{item.content.description}</p>
                </li>
            ))}
        </ul>
    );
};
export const App = () => (
    <Admin dataProvider={searchDataProvider} dashboard={MyDashboard}>
        {/*...*/}
    </Admin>
);
Customizing The Result Items
By default, <SearchWithResult> displays the results in <SearchResultsPanel>, which displays each results in a <SearchResultItem>. So rendering <SearchWithResult> without children is equivalent to rendering:
const MySearch = () => (
    <SearchWithResult>
        <SearchResultsPanel>
            <SearchResultItem />
        </SearchResultsPanel>
    </SearchWithResult>
);
<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 { Card, CardContent } from '@mui/material';
import Groups3Icon from '@mui/icons-material/Groups3';
import LibraryMusicIcon from '@mui/icons-material/LibraryMusic';
import { Admin } from 'react-admin';
import {
    SearchWithResult,
    SearchResultsPanel,
    SearchResultItem,
    useSearchResults,
} from '@react-admin/ra-search';
import { searchDataProvider } from './searchDataProvider';
const MyDashboard = () => (
    <Card>
        <CardContent>
            <SearchWithResult>
                <SearchResultsPanel>
                    <SearchResultItem
                        label={record => (
                            <>
                                {record.type === 'artists' ? (
                                    <Groups3Icon />
                                ) : (
                                    <LibraryMusicIcon />
                                )}
                                <span>{record.content.label}</span>
                            </>
                        )}
                    />
                </SearchResultsPanel>
            </SearchWithResult>
        </CardContent>
    </Card>
);
export const App = () => (
    <Admin dataProvider={searchDataProvider} dashboard={MyDashboard}>
        {/*...*/}
    </Admin>
);
You can also completely replace the search result item component:
import { Card, CardContent } from '@mui/material';
import { Link } from 'react-router-dom';
import { Admin } from 'react-admin';
import {
    SearchWithResult,
    SearchResultsPanel,
    SearchResultItem,
} from '@react-admin/ra-search';
import { searchDataProvider } from './searchDataProvider';
const MySearchResultItem = ({ data }) => (
    <li key={data.id} className="highlight">
        <Link to={data.url}>
            <strong>{data.content.label}</strong>
        </Link>
        <p>{data.content.description}</p>
    </li>
);
const MyDashboard = () => (
    <Card>
        <CardContent>
            <SearchWithResult>
                <SearchResultsPanel>
                    <MySearchResultItem />
                </SearchResultsPanel>
            </SearchWithResult>
        </CardContent>
    </Card>
);
export const App = () => (
    <Admin dataProvider={searchDataProvider} dashboard={MyDashboard}>
        {/*...*/}
    </Admin>
);
Tip: You can customize the highlight of the search terms by overriding the <SearchResultsPanel sx> prop as following:
const CustomSearch = () => (
    <SearchWithResult>
        <SearchResultsPanel
            sx={{
                '& ::highlight(search)': {
                    backgroundColor: '#7de5fa',
                },
            }}
        />
    </SearchWithResult>
);
Use It With SolarLayout
The <SearchWithResult> component works perfectly when used inside the <SolarLayout> menu.
The useSolarSidebarActiveMenu hook combined with the onNavigate prop allow you to close the <SolarMenu> when the user selects an element in the result.
Here is an implementation example:
import type { ReactNode } from 'react';
import { Admin } from 'react-admin';
import { Box } from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import AlbumIcon from '@mui/icons-material/Album';
import Groups3Icon from '@mui/icons-material/Groups3';
import {
    SolarLayout,
    SolarMenu,
    useSolarSidebarActiveMenu,
} from '@react-admin/ra-navigation';
import { SearchWithResult } from '@react-admin/ra-search';
import { searchDataProvider } from './searchDataProvider';
const MySolarLayout = ({ children }: { children: ReactNode }) => (
    <SolarLayout menu={MySolarMenu}>
        {children}
    </SolarLayout>
);
const MySolarMenu = () => (
    <SolarMenu bottomToolbar={<CustomBottomToolbar />}>
        <SolarMenu.Item
            name="artists"
            to="/artists"
            icon={<Groups3Icon />}
            label="resources.stores.name"
        />
        <SolarMenu.Item
            name="songs"
            to="/songs"
            icon={<AlbumIcon />}
            label="resources.events.name"
        />
    </SolarMenu>
);
const CustomBottomToolbar = () => (
    <>
        <SearchMenuItem />
        <SolarMenu.LoadingIndicatorItem />
    </>
);
const SearchMenuItem = () => {
    const [, setActiveMenu] = useSolarSidebarActiveMenu();
    const handleClose = () => {
        setActiveMenu('');
    };
    return (
        <SolarMenu.Item
            icon={<SearchIcon />}
            label="Search"
            name="search"
            subMenu={
                <Box sx={{ maxWidth: 298 }}>
                    <SearchWithResult onNavigate={handleClose} />
                </Box>
            }
            data-testid="search-button"
        />
    );
};
export const App = () => (
    <Admin dataProvider={searchDataProvider} layout={MySolarLayout}>
        {/*...*/}
    </Admin>
);
