<StackedFilters>
This Enterprise Edition component provides an alternative filter UI for <List>
pages. It introduces the concept of operators to allow richer filtering.
Usage
Use a <StackedFilters>
component in the <List actions>
element. Define the filters using the config
prop, which must contain a filtering configuration.
// in src/posts/PostList.tsx
import {
BooleanField,
Datagrid,
List,
NumberField,
ReferenceArrayField,
TextField,
} from 'react-admin';
import { PostListToolbar } from './PostListToolbar';
const PostList = () => (
<List actions={<PostListToolbar />}>
<Datagrid>
<TextField source="title" />
<NumberField source="views" />
<ReferenceArrayField tags="tags" source="tag_ids" />
<BooleanField source="published" />
</Datagrid>
</List>
);
// in src/posts/PostListToolbar.tsx
import { CreateButton, TopToolbar } from 'react-admin';
import {
StackedFilters,
FiltersConfig,
textFilter,
numberFilter,
referenceFilter,
booleanFilter,
} from '@react-admin/ra-form-layout';
const postListFilters: FiltersConfig = {
title: textFilter(),
views: numberFilter(),
tag_ids: referenceFilter({ reference: 'tags' }),
published: booleanFilter(),
};
export const PostListToolbar = () => (
<TopToolbar>
<CreateButton />
<StackedFilters config={postListFilters} />
</TopToolbar>
);
You must also update your data provider to support filters with operators. See the data provider configuration section below.
Filters Configuration
Define the filter configuration in the <StackedFilters config>
prop. The value must be an object defining the operators and UI for each field that can be used as a filter.
It looks like this:
import { FiltersConfig, StackedFilters, textFilter } from '@react-admin/ra-form-layout';
import { NumberInput, TextInput } from 'react-admin';
import { MyNumberRangeInput } from './MyNumberRangeInput';
const postListFilters: FiltersConfig = {
title: textFilter(),
views: {
operators: [
{ value: 'eq', label: 'Equals', type: 'single' },
{ value: 'neq', label: 'Not Equals', type: 'single', defaultValue: 0 },
{
value: 'between',
label: 'Between',
input: ({ source }) => <MyNumberRangeInput source={source} />,
type: 'multiple'
},
],
input: ({ source }) => <NumberInput source={source} />,
},
description: {
operators: [
{ value: 'eq', label: 'Equals', type: 'single' },
{ value: 'neq', label: 'Not Equals', type: 'single' },
],
input: ({ source }) => <TextInput source={source} />,
defaultValue: 'Lorem Ipsum',
}
};
export const PostListToolbar = () => (
<TopToolbar>
// ...
<StackedFilters config={postListFilters} />
</TopToolbar>
);
For a given field, the filter configuration should be an object containing an array of operators
and a default input
, used for operators that don’t define their own. You can use the filter configuration builders (like textFilter
) to build filter configuration objects.
An operator is an object that has a label
, a value
, a defaultValue
and a type
.
- The
label
is a string, and can be a translation key. - The
value
is used as a suffix to thesource
and passed to the list filters. - The
defaultValue
is used as the default filter value. - The
type
ensures that when selecting an operator with a different type than the previous one, React-admin resets the filter value. Its value should be eithersingle
for filters that accepts a single value (for instance astring
) ormultiple
for filters that accepts multiple values (for instance anArray
ofstring
). Should you need to differentiate a custom input from those two types, you may provide any type you want to thetype
option (for instance,map
).
For instance, if the user adds the views
filter with the eq
operator and a value of 0
, the dataProvider.getList()
will receive the following filter
parameter:
{ views_eq: 0 }
In your filter declaration, you can provide an operator
, an input
and a defaultValue
.
The input is a react object taking source
as prop and rendering the input you will need to fill for your filter.
Tip: The defaultValue
of an operator
takes priority over the defaultValue
of a filter.
Filter Configuration Builders
To make it easier to create a filter configuration, ra-form-layout
provides some helper functions. Each of them has predefined operators and inputs. They accept an array of operators if you want to remove some of them.
textFilter
: A filter for text fields. Defines the following operator:eq
,neq
andq
.numberFilter
: A filter for number fields. Defines the following operator:eq
,neq
,lt
andgt
.dateFilter
: A filter for date fields. Defines the following operator:eq
,neq
,lt
andgt
.booleanFilter
: A filter for boolean fields. Defines the following operator:eq
.choicesFilter
: A filter for fields that accept a value from a list of choices. Defines the following operator:eq
,neq
,eq_any
andneq_any
.choicesArrayFilter
: A filter for array fields. Defines the following operator:inc
,inc_any
andninc_any
.referenceFilter
: A filter for reference fields. Defines the following operator:eq
,neq
,eq_any
andneq_any
.
Build your filter configuration by calling the helpers for each source:
import {
FiltersConfig,
textFilter,
numberFilter,
referenceFilter,
booleanFilter,
} from '@react-admin/ra-form-layout';
const postListFilters: FiltersConfig = {
title: textFilter(),
views: numberFilter(),
tag_ids: referenceFilter({ reference: 'tags' }),
published: booleanFilter(),
};
Data Provider Configuration
In react-admin, dataProvider.getList()
accepts a filter
parameter to filter the records. There is no notion of operators in this parameter, as the expected format is an object like { field: value }
. As StackedFilters
needs operators, it uses a convention to concatenate the field name and the operator with an underscore.
For instance, if the Post resource has a title
field, and you configure <StackedFilters>
to allow filtering on this field as a text field, the dataProvider.getList()
may receive the following filter
parameter:
- title_eq
- title_neq
- title_q
The actual suffixes depend on the type of filter configured in <StackedFilter>
(see filters configuration builders above). Here is an typical call to dataProvider.getList()
with a posts list using <StackedFilters>
:
const { data } = useGetList('posts', {
filter: {
title_q: 'lorem',
date_gte: '2021-01-01',
views_eq: 0,
tags_inc_any: [1, 2],
},
pagination: { page: 1, perPage: 10 },
sort: { field: 'title', order: 'ASC' },
});
It’s up to your data provider to convert the filter
parameter into a query that your API understands.
For instance, if your API expects filters as an array of criteria objects ([{ field, operator, value }]
), dataProvider.getList()
should convert the filter
parameter as follows:
const dataProvider = {
// ...
getList: async (resource, params) => {
const { filter } = params;
const filterFields = Object.keys(filter);
const criteria = [];
// eq operator
filterFields.filter(field => field.endsWith('_eq')).forEach(field => {
criteria.push({ field: field.replace('_eq', ''), operator: 'eq', value: filter[field] });
});
// neq operator
filterFields.filter(field => field.endsWith('_neq')).forEach(field => {
criteria.push({ field: field.replace('_neq', ''), operator: 'neq', value: filter[field] });
});
// q operator
filterFields.filter(field => field.endsWith('_q')).forEach(field => {
criteria.push({ field: field.replace('_q', ''), operator: 'q', value: filter[field] });
});
// ...
},
}
Few of the existing data providers implement this convention. this means you’ll probably have to adapt your data provider to support the operators used by <StackedFilters>
.
Props
Prop | Required | Type | Default | Description |
---|---|---|---|---|
config |
Required | object | - | The stacked filters configuration |
BadgeProps |
Optional | object | - | Additional props to pass to the MUI Badge element |
ButtonProps |
Optional | object | - | Additional props to pass to the Button element |
className |
Optional | string | - | Additional CSS class applied on the root component |
PopoverProps |
Optional | Object | - | Additional props to pass to the MUI Popover element |
StackedFilters FormProps |
Optional | Object | - | Additional props to pass to the StackedFiltersForm element |
sx |
Optional | Object | - | An object containing the MUI style overrides to apply to the root component |
BadgeProps
This prop lets you pass additional props for the MUI Badge.
import { StackedFilters } from '@react-admin/ra-form-layout';
export const MyStackedFilter = () => (
<StackedFilters config={config} BadgeProps={{ showZero: true }} />
);
ButtonProps
This prop lets you pass additional props for the Button.
import { StackedFilters } from '@react-admin/ra-form-layout';
export const MyStackedFilter = () => (
<StackedFilters config={config} ButtonProps={{ variant: 'contained' }} />
);
className
This prop lets you pass additional CSS classes to apply to the root element (a div
).
import { StackedFilters } from '@react-admin/ra-form-layout';
export const MyStackedFilter = () => (
<StackedFilters config={config} className="my-css-class" />
);
config
This prop lets you define the filter configuration, which is required. This is an object defining the operators and UI for each source that can be used as a filter:
import { FiltersConfig, StackedFilters } from '@react-admin/ra-form-layout';
import { NumberInput } from 'react-admin';
import { MyNumberRangeInput } from './MyNumberRangeInput';
const postListFilters: FiltersConfig = {
views: {
operators: [
{ value: 'eq', label: 'Equals', type: 'single' },
{ value: 'neq', label: 'Not Equals', type: 'single', defaultValue: 1 },
{
value: 'between',
label: 'Between',
input: ({ source }) => <MyNumberRangeInput source={source} />,
type: 'multiple',
},
],
input: ({ source }) => <NumberInput source={source} />,
defaultValue: 0
},
};
export const MyStackedFilter = () => (
<StackedFilters config={postListFilters} />
);
PopoverProps
This prop lets you pass additional props for the MUI Popover.
import { StackedFilters } from '@react-admin/ra-form-layout';
export const MyStackedFilter = () => (
<StackedFilters config={config} PopoverProps={{ elevation: 4 }} />
);
StackedFiltersFormProps
This prop lets you pass additional props for the StackedFiltersForm.
import { StackedFilters } from '@react-admin/ra-form-layout';
export const MyStackedFilter = () => (
<StackedFilters config={config} StackedFiltersForm={{ className: 'my-css-class' }} />
);
sx
: CSS API
This prop lets you override the styles of the inner components thanks to the sx
property. This property accepts the following subclasses:
Rule name | Description |
---|---|
RaStackedFilters |
Applied to the root component |
& .RaStackedFilters-popover |
Applied to the MUI Popover |
& .RaStackedFilters-formContainer |
Applied to the form container (a div ) |
Internationalization
The source field names are translatable. ra-form-layout
uses the react-admin resource and field name translation system.
This is an example of an English translation file for the customer
resource:
// in i18n/en.js
export default {
resources: {
customer: {
name: 'Customer |||| Customers',
fields: {
first_name: 'First name',
last_name: 'Last name',
dob: 'Date of birth',
},
},
},
};
<StackedFilters>
also supports internationalization for operators. To leverage it, pass a translation key as the operator label:
import { FiltersConfig } from '@react-admin/ra-form-layout';
import DateRangeInput from './DateRangeInput';
const MyFilterConfig: FiltersConfig = {
published_at: {
operators: [
{
value: 'between',
label: 'resources.posts.filters.operators.between',
type: 'mutliple',
},
{
value: 'nbetween',
label: 'resources.posts.filters.operators.nbetween',
type: 'mutliple',
},
],
input: ({ source }) => <DateRangeInput source={source} />,
},
};
<StackedFiltersForm>
This component is responsible for rendering the filtering form, and is used internally by <StackedFilters>
. You can use it if you want to use the filter form without the <FilterButton>
component, e.g. to always show the filter form.
Usage
Just like <StackedFilters>
, <StackedFiltersForm>
requires a filtering configuration as its config
prop value.
import {
Datagrid,
List,
TextField,
NumberField,
BooleanField,
ReferenceArrayField,
useListContext,
} from 'react-admin';
import {
StackedFiltersForm,
FiltersConfig,
textFilter,
referenceFilter,
booleanFilter,
dateFilter,
} from '@react-admin/ra-form-layout';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Card,
Typography,
} from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
const postListFilters: FiltersConfig = {
id: textFilter({ operators: ['eq', 'neq'] }),
title: textFilter(),
published_at: dateFilter(),
is_public: booleanFilter(),
tag_ids: referenceFilter({ reference: 'tags' }),
};
const PostListFiltersForm = () => {
const { filterValues } = useListContext();
return (
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="filters-content"
id="filters-header"
>
<Typography>
{Object.keys(filterValues).length ? `${Object.keys(filterValues).length} filter(s) applied` : 'Filters'}
</Typography>
</AccordionSummary>
<AccordionDetails id="filters-content">
<StackedFiltersForm config={postListFilters} />
</AccordionDetails>
</Accordion>
);
};
If you need to be notified when users have applied filters, pass a function to the onFiltersApplied
prop. This is useful if you want to close the filters container (<Modal>
, <Drawer>
, etc.).
Props
Prop | Required | Type | Default | Description |
---|---|---|---|---|
config |
Required | object | - | The stacked filters configuration |
className |
Optional | string | - | Additional CSS class applied on the root component |
onFiltersApplied |
Optional | Function | - | A function called when users click on the apply button |
sx |
Optional | Object | - | An object containing the MUI style overrides to apply to the root component |
className
This prop lets you pass additional CSS classes to apply to the root element (a Form
).
import { StackedFiltersForm } from '@react-admin/ra-form-layout';
export const MyStackedFilterForm = () => (
<StackedFiltersForm config={config} className="my-css-class" />
);
config
This prop lets you define the filter configuration, which is required. This is an object defining the operators and UI for each source that can be used as a filter:
import { FiltersConfig, StackedFiltersForm } from '@react-admin/ra-form-layout';
import { NumberInput } from 'react-admin';
import { MyNumberRangeInput } from './MyNumberRangeInput';
const postListFilters: FiltersConfig = {
views: {
operators: [
{ value: 'eq', label: 'Equals', type: 'single' },
{ value: 'neq', label: 'Not Equals', type: 'single', defaultValue: 1 },
{
value: 'between',
label: 'Between',
input: ({ source }) => <MyNumberRangeInput source={source} />,
type: 'mutliple',
},
],
input: ({ source }) => <NumberInput source={source} />,
defaultValue: 0,
},
};
export const MyStackedFiltersForm = () => (
<StackedFiltersForm config={postListFilters} />
);
onFiltersApplied
This prop lets you provide a function that will be called when users click the apply button:
import { FiltersConfig } from '@react-admin/ra-form-layout';
export const MyStackedFiltersForm = () => (
<StackedFiltersForm config={config} onFiltersApplied={() => alert('Filters applied')} />
);
sx
: CSS API
This prop lets you override the styles of the inner components thanks to the sx
property. This property accepts the following subclasses:
Rule name | Description |
---|---|
RaStackedFiltersForm |
Applied to the root component |
& .RaStackedFiltersForm-sourceInput |
Applied to the AutocompleteInput that allows users to select the field |
& .RaStackedFiltersForm-operatorInput |
Applied to the SelectInput that allows users to select the field |
& .RaStackedFiltersForm-valueInput |
Applied to the input that allows users to set the filter value |