Docyrus

DataTableSideFilters

E-commerce-style facet filters for React Native — a "Filters (n)" trigger that opens a large bottom sheet (or an inline panel on tablets), covering every column type and emitting the same RuleGroupType JSON as the Query Builder. API-aligned with the web DataTableSideFilters.

iOSAndroid
Preview DataTableSideFilters on your device

Scan with Expo Go

Download Expo Go, then scan the QR code to preview native components.

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-data-table-side-filters
Required Packages(3 packages)
pnpm add @react-querybuilder/core date-fns tailwind-variants

DataTableSideFilters is built on the same ColumnConfig schema as DataTableFilter, so one column definition powers both the filter wizard and the facet panel. useDataTableSideFilters wraps useDataTableFilters and translates the internal FiltersState to and from a react-querybuilder RuleGroupType, so the emitted query feeds the Query Builder, saved views and your back end unchanged.

Usage

import { useState } from 'react';

import {
  DataTableSideFilters,
  useDataTableSideFilters,
  type RuleGroupType
} from '@/components/docyrus-native/data-table-side-filters';

const columnsConfig = [
  {
    id: 'category',
    displayName: 'Category',
    icon: 'fal bag-shopping',
    type: 'option' as const,
    accessor: (row: Product) => row.category,
    options: categoryOptions
  },
  {
    id: 'vendorId',
    displayName: 'Vendor',
    icon: 'fal user',
    type: 'multiOption' as const,
    accessor: (row: Product) => [row.vendorId],
    asyncOptions: {
      load: async ({ search, page, pageSize, signal }) => {
        const res = await fetch(`${API}/vendors?q=${search}&page=${page}&size=${pageSize}`, { signal });

        return res.json(); // → { items: ColumnOption[], hasMore: boolean }
      }
    }
  },
  {
    id: 'price',
    displayName: 'Price',
    icon: 'fal dollar-sign',
    type: 'number' as const,
    accessor: (row: Product) => row.price,
    min: 0,
    max: 500
  },
  {
    id: 'releasedAt',
    displayName: 'Released',
    icon: 'fal calendar',
    type: 'date' as const,
    accessor: (row: Product) => row.releasedAt
  },
  {
    id: 'inStock',
    displayName: 'In stock',
    icon: 'fal circle-check',
    type: 'boolean' as const,
    accessor: (row: Product) => row.inStock
  }
] as const;

export function ProductFilters({ products }: { products: Product[] }) {
  const [query, setQuery] = useState<RuleGroupType>({ combinator: 'and', rules: [] });

  const { columns, filters, actions, strategy } = useDataTableSideFilters({
    strategy: 'client',
    data: products,
    columnsConfig,
    query,
    onQueryChange: setQuery
  });

  return (
    <DataTableSideFilters
      columns={columns}
      filters={filters}
      actions={actions}
      strategy={strategy}
      title="Filter products"
      searchable="name" />
  );
}

Presentation

  • presentation="sheet" (default): renders an outline Filters (n) button. Pressing it opens a large-detent ActionSheet with Clear all on the left and Done on the right. The search input and the active-filter chips sit in the sheet's fixed header, and the sections scroll below them. Filters apply live, so Done only closes the sheet. Replace the button with renderTrigger, and control visibility with open / defaultOpen / onOpenChange.
  • presentation="inline": renders the panel in place (tablets, a dedicated filter screen). With collapsible, the header gets a collapse button and the collapsed state is a full-width bar (Filters · n) that expands on press.
<DataTableSideFilters
  {...filterProps}
  presentation="inline"
  variant="bordered"
  collapsible
  defaultExpanded={false} />

Render mode resolution

Each column picks its UI by column.type. You can override the mode with defaults[columnId].mode:

Column typeDefault render modeNative UI
texttext-inputDebounced input (300 ms), applies contains.
numbernumeric-rangeMin / Max inputs, clamped to the faceted or column.min / column.max bounds. Shows an error when min > max.
datedate-rangePreset chips (Today, Yesterday, Last 7 days, Last 30 days, This week, This month, This year) plus From / To DateTimePickers.
booleanbooleanAny / True / False ToggleGroup. "Any" removes the filter.
option / multiOptioninline-checkboxCheckbox rows with faceted counts. Past showMoreThreshold (default 8) the list collapses behind Show more, with selected options pinned above the fold.
option / multiOption with asyncOptionsdropdown-chipsA trigger that opens a searchable, paginated picker sheet, with the selection shown as chips.

Set defaults[columnId].mode = 'dropdown-chips' to move a long static option list into a picker sheet.

Sections, sticky filters and Show more

<DataTableSideFilters
  {...filterProps}
  defaults={{
    inStock: { sticky: true },
    tags: { showMoreThreshold: 6 },
    rating: { collapsed: true },
    internalCode: { hidden: true }
  }}
  sections={[
    { id: 'shop', title: 'Shop', columnIds: ['category', 'brand', 'vendorId'] },
    { id: 'pricing', title: 'Pricing & Quality', columnIds: ['price', 'rating'] }
  ]} />

Without sections, sticky columns render first and the rest follow. With sections, columns you did not list fall into an Other group at the end.

Query JSON output

useDataTableSideFilters emits a RuleGroupType through onQueryChange whenever a filter changes, and returns it as query. Operators are mapped through DEFAULT_OPERATOR_MAP (for example contains → like, is between → between, is any of → in, include any of → contains any). Pass operatorMap to use a different vocabulary. Date values are serialized as timezone-naive yyyy-MM-dd'T'HH:mm:ss strings. For date-only columns, the start side is snapped to 00:00:00 and the end side to 23:59:59, so the whole boundary day is included.

API Reference

DataTableSideFiltersProps

PropTypeDefaultDescription
columnsColumn<TData>[]—Columns from useDataTableSideFilters() (required).
filtersFiltersState—Current filter state (required).
actionsDataTableFilterActions—Filter actions from the hook (required).
strategyFilterStrategy—'client' or 'server' (required).
defaultsSideFilterDefaults—Per-column UI hints (mode, threshold, collapsed, sticky, title, hidden).
sectionsReadonlyArray<SideFilterSectionGroup>—Group sections under named headings.
localeLocale'en'Bundled filter-core string catalog. UiTranslationProvider keys win.
variant'default' | 'bordered' | 'compact''default'Panel style (bordered = rounded card with padding).
classNamestring—Classes for the trigger button (sheet), the panel (inline) or the collapsed bar.
titleReactNodet('ui.dataTableSideFilters.title', 'Filters')Header title. Pass null to hide it. The sheet title only shows string titles.
showActiveChipsbooleantrueShow removable chips for the active filters.
showClearAllbooleantrueShow Clear all (n) while any filter is active.
searchableboolean | stringfalsetrue drives the first text column with a search input; a column id drives that column. The searched column is removed from the sections.
clearAllLabelstringt('ui.dataTableSideFilters.clearAll', 'Clear all')"Clear all" label.
clearLabelstringt('ui.dataTableSideFilters.clear', 'Clear')Per-section "Clear" label.
presentation'sheet' | 'inline''sheet'Native: trigger + bottom sheet, or the panel in place.
triggerLabelstringtitle, then 'Filters'Native (sheet): trigger label. The active count is appended as (n).
renderTrigger(props: { open: () => void; activeCount: number; label: string }) => ReactNode—Native (sheet): replace the trigger button.
openboolean—Native (sheet): controlled sheet visibility.
defaultOpenbooleanfalseNative (sheet): initial sheet visibility (uncontrolled).
onOpenChange(open: boolean) => void—Native (sheet): called when the sheet opens or closes.
collapsiblebooleanfalseInline: the panel can collapse to a compact bar.
expandedboolean—Inline: controlled expanded state.
defaultExpandedbooleantrueInline: uncontrolled initial expanded state.
onExpandedChange(expanded: boolean) => void—Inline: called when the user collapses or expands the panel.
collapsedWidthnumber | string—Accepted for web parity. The collapsed bar is full-width on native.
expandLabelReactNodetitle, then 'Filters'Inline: label of the collapsed bar.
collapseAriaLabelstringt('ui.dataTableSideFilters.collapse', 'Collapse filters')Accessibility label of the collapse button.
expandAriaLabelstringt('ui.dataTableSideFilters.expand', 'Expand filters')Accessibility label of the collapsed bar.

useDataTableSideFilters options (DataTableSideFiltersOptions)

Extends the useDataTableFilters options (without defaultFilters / filters / onFiltersChange, which the hook owns).

OptionTypeDefaultDescription
strategyFilterStrategy—'client' (in-memory facets) or 'server' (required).
dataTData[]—Rows used for faceting (required; pass [] for server strategy).
columnsConfigReadonlyArray<ColumnConfig<TData>>—Column definitions (required).
calculateFacetsbooleantrueCompute faceted counts / min-max from data.
optionsPartial<Record<OptionColumnId, ColumnOption[]>>—External options per option column.
facetedPartial<Record<ColumnId, Map<string, number> | [number, number]>>—External facets per column.
defaultQueryRuleGroupType—Uncontrolled initial query, translated to FiltersState on mount.
queryRuleGroupType—Controlled query. Pair it with onQueryChange.
onQueryChange(query: RuleGroupType) => void—Receives the translated query on every change.
combinator'and' | 'or''and'Top-level combinator of the emitted group.
operatorMapSideFilterOperatorMapDEFAULT_OPERATOR_MAPFilter operator → query-builder operator vocabulary.
defaultsSideFilterDefaults—Passed through in the result so you can spread it onto the component.
sectionsReadonlyArray<SideFilterSectionGroup>—Passed through in the result.

The hook returns { columns, filters, actions, strategy, query, defaults, sections, reset }. reset() clears every filter.

SideFilterColumnDefaults

FieldTypeDescription
modeSideFilterRenderMode'auto' | 'text-input' | 'inline-checkbox' | 'dropdown-chips' | 'date' | 'date-range' | 'numeric-range' | 'boolean'.
showMoreThresholdnumberOptions shown before Show more (default 8).
collapsedbooleanThe section starts collapsed.
stickybooleanPin the section above the unpinned sections.
titleReactNodeOverride the section title (default column.displayName).
hiddenbooleanRemove the column from the panel.

SideFilterSectionGroup

FieldTypeDescription
idstringGroup id.
titleReactNodeGroup heading.
columnIdsReadonlyArray<string>Columns in this group, in order.
withDividerbooleanDivider above the heading (default true).

Building blocks

ComponentProps
SideFilterSectionid, title, icon?, activeCount? (0), defaultCollapsed? (false), onClear?, clearLabel? ('Clear'), children
SideFilterActiveChipsfilters, columns, actions, clearAllLabel?, layout? ('scroll' | 'wrap', default 'scroll')
SideFilterClearcount, onClick, label? ('Clear all'), className?. Renders nothing while count is 0.
SideFilterSearchcolumnId, columns, filters, actions, locale?, placeholder?
SideFilterTextcolumn, filter?, actions, locale?, placeholder?
SideFilterNumericRangecolumn, filter?, actions, locale?
SideFilterDateRangecolumn, filter?, actions, locale?, withPresets? (default true)
SideFilterBooleancolumn, filter?, actions, locale?
SideFilterCheckboxListcolumn, filter?, actions, locale?, threshold?
SideFilterOptionsDropdowncolumn, filter?, actions, locale?, placeholder?
SideFilterAsyncOptionscolumn, filter?, actions, locale?, placeholder?

Native differences

  • A persistent side column does not fit a phone, so the default presentation is a trigger plus a large bottom sheet. The web layout is available as presentation="inline".
  • Numeric ranges use Min / Max inputs without a slider.
  • Option pickers (dropdown-chips and async options) open a picker sheet instead of a popover.
  • New native-only props: presentation, triggerLabel, renderTrigger, open, defaultOpen, onOpenChange, and layout on SideFilterActiveChips. collapsedWidth is accepted but ignored.
  • Column icons are DocyrusIcon names (for example 'fal tag'), elements or components, instead of Lucide icons.

Translation keys

Panel strings use ui.dataTableSideFilters.* with English fallbacks: title, clearAll, clear, other, done, expand, collapse, any, selected, showMore, showLess, minMaxOrder, asyncNotConfigured. Filter-core strings (search, min / max, true / false, date presets) come from the ui.dataTableFilter.* keys shared with DataTableFilter.

Components

ComponentDescription
DataTableSideFiltersTrigger + sheet, or the inline panel.
SideFilterSectionCollapsible per-column section with active count and Clear.
SideFilterActiveChipsRemovable chips for every active filter (usable outside the panel).
SideFilterClear"Clear all (n)" link.
SideFilterSearchDebounced search bound to one text column.
SideFilterText / SideFilterNumericRange / SideFilterDateRange / SideFilterBoolean / SideFilterCheckboxList / SideFilterOptionsDropdown / SideFilterAsyncOptionsPer-mode controllers.

Helpers

ExportDescription
useDataTableSideFilters(options)Filter state + query translation.
filtersStateToRuleGroup(filters, combinator, operatorMap, columns)FiltersState → RuleGroupType.
ruleGroupToFiltersState(query, columns, operatorMap)RuleGroupType → FiltersState.
DEFAULT_OPERATOR_MAPDefault operator vocabulary.
dataTableSideFiltersVariantstv() variants of the panel.

Type Exports

TypeDescription
DataTableSideFiltersPropsComponent props.
DataTableSideFiltersPresentation'sheet' | 'inline'.
DataTableSideFiltersOptionsHook options.
UseDataTableSideFiltersReturnHook result.
SideFilterRenderModeRender modes.
SideFilterColumnDefaults / SideFilterDefaultsPer-column hints.
SideFilterSectionGroupSection grouping.
SideFilterCombinator'and' | 'or'.
SideFilterOperatorMapOperator vocabulary map.
RuleGroupTypeRe-exported from @react-querybuilder/core.

On this page