<FilterList>
An alternative UI to the Filter Button/Form Combo is the FilterList Sidebar. Similar to what users usually see on e-commerce websites, it’s a panel with many simple filters that can be enabled and combined using the mouse.
The user experience is better than the Button/Form Combo, because the filter values are explicit, and it doesn’t require typing anything in a form. But it’s a bit less powerful, as only filters with a finite set of values (or intervals) can be used in the <FilterList>
.
Usage
Use the <FilterList>
component in a sidebar for the <List>
view. It expects a list of <FilterListItem>
as children. Each <FilterListItem>
defines a filter label
and a value
, which is merged with the current filter value when enabled by the user.
For instance, here is a filter sidebar for a post list, allowing users to filter on two fields:
import { SavedQueriesList, FilterLiveSearch, FilterList, FilterListItem } from 'react-admin';
import { Card, CardContent } from '@mui/material';
import MailIcon from '@mui/icons-material/MailOutline';
import CategoryIcon from '@mui/icons-material/LocalOffer';
export const PostFilterSidebar = () => (
<Card sx={{ order: -1, mr: 2, mt: 9, width: 200 }}>
<CardContent>
<SavedQueriesList />
<FilterLiveSearch />
<FilterList label="Subscribed to newsletter" icon={<MailIcon />}>
<FilterListItem label="Yes" value={{ has_newsletter: true }} />
<FilterListItem label="No" value={{ has_newsletter: false }} />
</FilterList>
<FilterList label="Category" icon={<CategoryIcon />}>
<FilterListItem label="Tests" value={{ category: 'tests' }} />
<FilterListItem label="News" value={{ category: 'news' }} />
<FilterListItem label="Deals" value={{ category: 'deals' }} />
<FilterListItem label="Tutorials" value={{ category: 'tutorials' }} />
</FilterList>
</CardContent>
</Card>
);
Add this component to the list view using the <List aside>
prop:
import { PostFilterSidebar } from './PostFilterSidebar';
export const PostList = () => (
<List aside={<PostFilterSidebar />}>
...
</List>
);
Tip: The <Card sx>
prop in the PostFilterSidebar
component above is here to put the sidebar on the left side of the screen, instead of the default right side.
A more sophisticated example is the filter sidebar for the visitors list visible in the screencast at the beginning of this page. The code for this example is available in the react-admin repository.
Tip: In a Filter List sidebar, you can use the <FilterLiveSearch>
component to add a search input at the top of the sidebar, and the <SavedQueriesList>
component to add a list of saved queries.
Props
<FilterList>
accepts 3 props:
Prop | Required | Type | Default | Description |
---|---|---|---|---|
children |
Required | node | The children of <FilterList> must be a list of <FilterListItem> components. |
|
icon |
Optional | element | When set, the <FilterList icon> prop appears on the left side of the filter label. |
|
label |
Optional | string | React-admin renders the <FilterList label> on top of the child filter items. The string is passed through the useTranslate hook, and therefore can be translated. |
children
The children of <FilterList>
must be a list of <FilterListItem>
components. Each <FilterListItem>
defines a filter label
and a value
, which is merged with the current filter value when enabled by the user.
import { FilterList, FilterListItem } from 'react-admin';
const HasNewsletterFilter = () => (
<FilterList label="Has newsletter">
<FilterListItem
label="True"
value={{ has_newsletter: true }}
/>
<FilterListItem
label="False"
value={{ has_newsletter: false }}
/>
</FilterList>
);
icon
When set, the <FilterList icon>
prop appears on the left side of the filter label.
import { FilterList, FilterListItem } from 'react-admin';
import MonetizationOnIcon from '@mui/icons-material/MonetizationOnOutlined';
const HasOrderedFilter = () => (
<FilterList
label="Has ordered"
icon={<MonetizationOnIcon />}
>
<FilterListItem
label="True"
value={{ nb_commands_gte: 1, nb_commands_lte: undefined }}
/>
<FilterListItem
label="False"
value={{ nb_commands_gte: undefined, nb_commands_lte: 0 }}
/>
</FilterList>
);
label
React-admin renders the <FilterList label>
on top of the child filter items. The string is passed through the useTranslate
hook, and therefore can be translated.
import { FilterList, FilterListItem } from 'react-admin';
const HasOrderedFilter = () => (
<FilterList label="Has ordered">
<FilterListItem
label="True"
value={{ nb_commands_gte: 1, nb_commands_lte: undefined }}
/>
<FilterListItem
label="False"
value={{ nb_commands_gte: undefined, nb_commands_lte: 0 }}
/>
</FilterList>
);
Placing Filters In A Sidebar
You can place these <FilterList>
anywhere inside a <List>
. The most common case is to put them in a sidebar that is on the left-hand side of the Datagrid
. You can use the aside
property for that:
import * as React from 'react';
import { Box, Card, CardContent, styled } from '@mui/material';
import { LastVisitedFilter, HasOrderedFilter, HasNewsletterFilter, SegmentFilter } from './filters';
const FilterSidebar = () => (
<Box
sx={{
display: {
xs: 'none',
sm: 'block'
},
order: -1, // display on the left rather than on the right of the list
width: '15em',
marginRight: '1em',
}}
>
<Card>
<CardContent>
<LastVisitedFilter />
<HasOrderedFilter />
<HasNewsletterFilter />
<SegmentFilter />
</CardContent>
</Card>
</Box>
);
const CustomerList = props => (
<List aside={<FilterSidebar />}>
// ...
</List>
)
Tip: The <FilterList>
Sidebar is not a good UI for small screens. You can choose to hide it on small screens (as in the previous example). A good tradeoff is to use <FilterList>
on large screens, and the Filter Button/Form combo on Mobile.
Cumulative Filters
By default, selecting a filter item replaces the current filter value. But for some filter types, like categories, you may want to allow users to select more than one item.
To do so, you can use the isSelected
and toggleFilter
props of the <FilterListItem>
component.
- The
isSelected
prop accepts a function that receives the item value and the currently applied filters. It must return a boolean. - The
toggleFilter
prop accepts a function that receives the item value and the currently applied filters. It is called when user toggles a filter and must return the new filters to apply.
Here’s how you could implement cumulative filters, e.g. allowing users to filter items having one of several categories:
import { FilterList, FilterListItem } from 'react-admin';
import CategoryIcon from '@mui/icons-material/LocalOffer';
export const CategoriesFilter = () => {
const isSelected = (value, filters) => {
const categories = filters.categories || [];
return categories.includes(value.category);
};
const toggleFilter = (value, filters) => {
const categories = filters.categories || [];
return {
...filters,
categories: categories.includes(value.category)
// Remove the category if it was already present
? categories.filter(v => v !== value.category)
// Add the category if it wasn't already present
: [...categories, value.category],
};
};
return (
<FilterList label="Categories" icon={<CategoryIcon />}>
<FilterListItem
label="Tests"
value={{ category: 'tests' }}
isSelected={isSelected}
toggleFilter={toggleFilter}
/>
<FilterListItem
label="News"
value={{ category: 'news' }}
isSelected={isSelected}
toggleFilter={toggleFilter}
/>
<FilterListItem
label="Deals"
value={{ category: 'deals' }}
isSelected={isSelected}
toggleFilter={toggleFilter}
/>
<FilterListItem
label="Tutorials"
value={{ category: 'tutorials' }}
isSelected={isSelected}
toggleFilter={toggleFilter}
/>
</FilterList>
)
}
<FilterListItem>
The children of <FilterList>
must be a list of <FilterListItem>
components. The <FilterListItem>
accepts the following props:
Prop | Required | Type | Default | Description |
---|---|---|---|---|
label |
Required | string | The label of the filter item. It is passed through the useTranslate hook, and therefore can be translated. |
|
value |
Required | object | The value of the filter item. It is merged with the current filter value when enabled by the user. | |
icon |
Optional | ReactElement |
When set, the icon appears to the left of the item label. | |
isSelected |
Optional | function | A function that receives the item value and the currently applied filters. It must return a boolean. | |
toggleFilter |
Optional | function | A function that receives the item value and the currently applied filters. It is called when user toggles a filter and must return the new filters to apply. |
Using Inputs
If you want to add a simple text input to the sidebar, you can use the <FilterLiveSearch>
component alongside <FilterList>
in the <List>
sidebar. It will render a simple text input, which will filter the list based on the value entered by the user.
import { FilterLiveSearch, FilterList, FilterListItem } from 'react-admin';
import { Card, CardContent } from '@mui/material';
import MailIcon from '@mui/icons-material/MailOutline';
export const PostFilterSidebar = () => (
<Card sx={{ order: -1, mr: 2, mt: 6, width: 250, height: 'fit-content' }}>
<CardContent>
<FilterLiveSearch source="q" label="Search" />
<FilterList label="Subscribed to newsletter" icon={<MailIcon />}>
<FilterListItem label="Yes" value={{ has_newsletter: true }} />
<FilterListItem label="No" value={{ has_newsletter: false }} />
</FilterList>
</CardContent>
</Card>
);
If you want to use other type of inputs, such as a <ReferenceInput>
, you can use the <FilterLiveForm>
component to create a form that automatically updates the filters when the user changes the value of an input.
import * as React from 'react';
import CategoryIcon from '@mui/icons-material/LocalOffer';
import Person2Icon from '@mui/icons-material/Person2';
import TitleIcon from '@mui/icons-material/Title';
import { Card, CardContent } from '@mui/material';
import {
AutocompleteInput,
FilterLiveForm,
Datagrid,
FilterList,
FilterListItem,
FilterListSection,
List,
ReferenceField,
ReferenceInput,
TextField,
TextInput,
} from 'react-admin';
const BookListAside = () => (
<Card sx={{ order: -1, mr: 2, mt: 6, width: 250, height: 'fit-content' }}>
<CardContent>
<FilterList label="Century" icon={<CategoryIcon />}>
<FilterListItem
label="21st"
value={{ year_gte: 2000, year_lte: undefined }}
/>
<FilterListItem
label="20th"
value={{ year_gte: 1900, year_lte: 1999 }}
/>
<FilterListItem
label="19th"
value={{ year_gte: 1800, year_lte: 1899 }}
/>
</FilterList>
<FilterListSection label="Title" icon={<TitleIcon />}>
<FilterLiveForm>
<TextInput source="title" resettable helperText={false} />
</FilterLiveForm>
</FilterListSection>
<FilterListSection label="Author" icon={<Person2Icon />}>
<FilterLiveForm>
<ReferenceInput source="authorId" reference="authors">
<AutocompleteInput helperText={false} />
</ReferenceInput>
</FilterLiveForm>
</FilterListSection>
</CardContent>
</Card>
);
export const BookList = () => (
<List aside={<BookListAside />}>
<Datagrid>
{/* ... */}
</Datagrid>
</List>
);
Check out the <FilterLiveForm>
documentation for more information.