<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 } from 'react-admin';
import { MyNumberRangeInput } from './MyNumberRangeInput';

const postListFilters: FiltersConfig = {
    title: textFilter(),
    views: {
        operators: [
            { value: 'eq', label: 'Equals' },
            { value: 'neq', label: 'Not Equals' },
            {
                value: 'between',
                label: 'Between',
                input: ({ source }) => <MyNumberRangeInput source={source} />,
            },
        ],
        input: ({ source }) => <NumberInput source={source} />,
    },
};

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 and a value.

  • The label is a string, and can be a translation key.
  • The value is used as a suffix to the source and passed to the list filters.

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 }

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 and q.
  • numberFilter: A filter for number fields. Defines the following operator: eq, neq, lt and gt.
  • dateFilter: A filter for date fields. Defines the following operator: eq, neq, lt and gt.
  • 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 and neq_any.
  • choicesArrayFilter: A filter for array fields. Defines the following operator: inc, inc_any and ninc_any.
  • referenceFilter: A filter for reference fields. Defines the following operator: eq, neq, eq_any and neq_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' },
            { value: 'neq', label: 'Not Equals' },
            {
                value: 'between',
                label: 'Between',
                input: ({ source }) => <MyNumberRangeInput source={source} />,
            },
        ],
        input: ({ source }) => <NumberInput source={source} />,
    },
};

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',
            },
            {
                value: 'nbetween',
                label: 'resources.posts.filters.operators.nbetween',
            },
        ],
        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' },
            { value: 'neq', label: 'Not Equals' },
            {
                value: 'between',
                label: 'Between',
                input: ({ source }) => <MyNumberRangeInput source={source} />,
            },
        ],
        input: ({ source }) => <NumberInput source={source} />,
    },
};

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