<StackedFilters>
This Enterprise Edition component provides an alternative filter UI for <List>
pages. It lets users build complex filters by combining a field, an operator, and a value.
Usage
Create a filter configuration object by specifying the operators and UI for each source that can be used as a filter. Then, pass it to the <StackedFilters>
component, and pass that component to the filters
prop of the <List>
component.
import { Datagrid, List, TextField, NumberField, BooleanField, ReferenceArrayField } from 'react-admin';
import { StackedFilters, textFilter, dateFilter, referenceFilter, booleanFilter } from '@react-admin/ra-form-layout';
const postListFilters = {
id: textFilter({ operators: ['eq', 'neq'] }),
title: textFilter(),
published_at: dateFilter(),
is_public: booleanFilter(),
tags: referenceFilter({ reference: 'tags' }),
};
const PostList = () => (
<List filters={<StackedFilters config={postListFilters} />}>
<Datagrid>
<TextField source="title" />
<NumberField source="views" />
<ReferenceArrayField tags="tags" source="tag_ids" />
<BooleanField source="published" />
</Datagrid>
</List>
)
The config
prop accepts an object with the following structure:
{
[source]: {
operators: [operator],
input: ReactElement,
},
}
Check the built-in filters section for more information about helpers for building filters.
Data Provider Integration
Upon user interaction, <StackedFilters>
sets the list filter
with keys concatenating the selected source with the selected operator, separated by an underscore (_
). In the screencast above, the user selection triggers a call to the dataProvider
with the following filter:
dataProvider.getList('posts', {
filter: {
title_contains: 'volup',
is_public_eq: true,
},
sort: { field: 'id', order: 'DESC' },
pagination: { page: 1, perPage: 10 },
});
It’s your responsibility to handle these compound keys in your data provider. For instance, to handle the title_contains
filter, you can use the following code:
// in dataProvider.js
const dataProvider = {
getList: (resource, params) => {
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
let filter = params.filter;
if (resource === 'posts') {
if (filter.title_contains) {
filter = { ...filter, title: { ilike: `%${filter.title_contains}%` } };
delete filter.title_contains;
}
if (filter.is_public_eq) {
filter = { ...filter, is_public: filter.is_public_eq };
delete filter.is_public_eq;
}
}
const query = {
sort: JSON.stringify([field, order]),
range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]),
filter: JSON.stringify(params.filter),
};
const url = `${apiUrl}/${resource}?${stringify(query)}`;
return httpClient(url).then(({ headers, json }) => ({
data: json,
total: parseInt(headers.get('content-range').split('/').pop(), 10),
}));
},
};
Tip: Build a generic solution to handle compound keys for all resources in your data provider, or add support for compound keys in your API directly.
config
<StackedFilters>
requires a filter configuration object defining the possible fields and operators.
For convenience, react-admin provides a set of built-in filters for common field types (text, number, date, reference, etc).
import { StackedFilters, textFilter, dateFilter, referenceFilter, booleanFilter } from '@react-admin/ra-form-layout';
const postListFilters = {
id: textFilter({ operators: ['eq', 'neq'] }),
title: textFilter(),
published_at: dateFilter(),
is_public: booleanFilter(),
tags: referenceFilter({ reference: 'tags' }),
};
const PostList = () => (
<List filters={<StackedFilters config={postListFilters} />}>
{/* ... */}
</List>
);
You can also define your own filters. Each filter is an object that defines the operators and UI for a given source. For instance, the following filter configuration defines a filter for the views
field that uses a NumberInput
for the eq
and neq
operators, and a custom MyNumberRangeInput
for the between
operator.
import { NumberInput } from 'react-admin';
import { textFilter, referenceFilter, booleanFilter } from '@react-admin/ra-form-layout';
import { MyNumberRangeInput } from './MyNumberRangeInput';
const postListFilters = {
title: textFilter(),
views: {
operators: [
{ value: 'eq', label: 'Equals', input: ({ source }) => <NumberInput source={source} />, },
{ value: 'neq', label: 'Not Equals', input: ({ source }) => <NumberInput source={source} />, },
{ value: 'between', label: 'Between', input: ({ source }) => <MyNumberRangeInput source={source} /> },
],
},
tag_ids: referenceFilter({ reference: 'tags' }),
published: booleanFilter(),
};
An operator is an object with the shape { label, value, input }
.
label
: Operator input label. Can be a translation key.value
: used as a suffix to thesource
when creating the list filters.input
: a component accepting asource
prop. React-admin will pass the source of the filter to the input component (‘views’ in the above example)
For instance, with the source views
, the operator eq
, and value set to 0
, the dataProvider will receive the following filter
parameter:
{ views_eq: 0 }
Tip: You can define a default input for all operators of a filter by using the input
property of the filter object:
import { NumberInput } from 'react-admin';
import { textFilter, referenceFilter, booleanFilter } from '@react-admin/ra-form-layout';
const postListFilters = {
title: textFilter(),
views: {
input: ({ source }) => <NumberInput source={source} />,
operators: [
{ value: 'eq', label: 'Equals' },
{ value: 'neq', label: 'Not Equals' },
{ value: 'between', label: 'Between', input: ({ source }) => <MyNumberRangeInput source={source} /> },
],
},
tag_ids: referenceFilter({ reference: 'tags' }),
published: booleanFilter(),
};
Built-In Filters
To facilitate the creation of filter configurations, react-admin provides some helpers for common filter types. 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
.
The operators are defined as follows:
eq
: Equalsneq
: Not Equalsq
: Full-text searchlt
: Less thangt
: Greater thaneq_any
: Equals anyneq_any
: Not Equals anyinc
: Includesinc_any
: Includes anyninc_any
: Not Includes any
Use these built-in helpers to build your filter configuration:
import { textFilter, numberFilter, referenceFilter, booleanFilter } from '@react-admin/ra-form-layout';
const postListFilters = {
title: textFilter(),
views: numberFilter(),
tag_ids: referenceFilter({ reference: 'tags' }),
published: booleanFilter(),
};
With the configuration above, the possible filter keys are:
title_eq
title_neq
title_q
views_eq
views_neq
views_lt
views_gt
tag_ids_eq
tag_ids_neq
tag_ids_eq_any
tag_ids_neq_any
published_eq
Internationalization
React-admin uses the keys of the filter configuration to display the field names. Each key goes through the standard resource and field name translation system, so you can customize them using a translation file.
// in i18n/en.js
export default {
resources: {
posts: {
name: 'Post |||| Posts',
fields: {
title: 'Title',
views: '# views',
tag_ids: 'Tags',
published: 'Published',
}
},
},
};
<StackedFilters>
also uses internationalization for operators. To leverage it, pass a translation key as the operator label:
import DateRangeInput from './DateRangeInput';
const MyFilterConfig = {
published_at: {
operators: [
{
value: 'between',
label: 'resources.posts.filters.operators.between',
},
{
value: 'nbetween',
label: 'resources.posts.filters.operators.nbetween',
},
],
input: ({ source }) => <DateRangeInput source={source} />,
},
};
Then translate the operators in the translation messages:
// in i18n/en.js
export default {
resources: {
posts: {
name: 'Post |||| Posts',
filters: {
operators: {
between: 'Between',
nbetween: 'Not Between',
},
},
},
},
};