Skip to content

Filtering

function

declare function useFilters(doc: FilterableDocument, id: string, columns: readonly FilterColumn[], options?: FiltersOptions): Filters;

The filter inputs of one owner of a condition part: a grid’s filter row, a search form, a picker. One hook for all of an owner’s filters, because reading a condition back has to see all columns at once to tell whose term is whose.

const filters = useFilters(foos, "foo-grid", [
{field: "name", filter: operatorFilter("containsIgnoreCase")},
{field: "num", filter: operatorFilter("eq", "Int")}
])
filters.columns.map(column => <FilterInput key={ column.label } column={ column }/>)

The owner’s part of the condition is its component where the condition is a composition of components, all of it otherwise (see FilterDSL’s ownedPart() and updateComponent()); other owners’ components are left as they are. The part is one term per active column, combined with and(), plus the unclaimed terms.

When the part changes to something the owner didn’t write, the inputs show it: each column’s filter is asked to recognize a term (fromCondition), and what nobody recognizes shows as unclaimed. A term the owner wrote itself is not read back, so the input keeps what the user typed even where the filter normalizes it.

Changing a filter goes back to the first page.

Parameters

doc query document snapshot
id component id of the owner
columns the owner’s filters, in a fixed order
options delay before an input change is sent

interface

interface FilterableDocument

What useFilters() needs of a query document: its config and update(). A snapshot from useInjection() or useQueryDocument() is one as long as its query selects config.

type: string;

the row type, which the columns’ field paths start from

config: QueryConfig;
update(delta: QueryConfigDelta): Promise<unknown>;

interface

interface FiltersOptions

Options for useFilter()

delay?: number;

Milliseconds between the last change to an input and the update() it causes. Default 300.

interface

interface Filters

The filter state of one owner of a condition part.

columns: ColumnFilterState[];

per column, in the order given

unclaimed: FilterExpression[];

Terms of the owner’s part that no column recognizes: written by code that doesn’t know the columns’ filters, or recognized by more than one column. They stay in the part until reset().

active: boolean;

whether the owner’s part filters anything

reset(): Promise<void>;

Clears every input and the unclaimed terms, and updates the document right away.

interface

interface ColumnFilterState

The filter state of one column.

field: FieldExpression;

what the column filters: a field path or an expression

label: string;

what the inputs’ accessible names call it: the column’s label, or its field’s heading, "Foo.name"

filter: ColumnFilter<any>;

the column’s filter

values: unknown[];

current input values, null for an empty input

setValues(values: unknown[]): void;

Replaces the input values. The document is updated after the delay, once the inputs have stopped changing, and only if every input is filled, or any for a partial filter; until then the column filters nothing.

active: boolean;

whether the column’s term is part of the document’s condition

function

declare function FilterInput({ column, className }: FilterInputProps): JSX.Element;

The inputs of one column’s filter: the filter’s own Input component where it has one, otherwise one text input per value.

Built on useFilters() and nothing else, so an input that has to look different is written the same way.

type

type FilterInputProps = {
/**
* The column's filter state, one of `useFilters().columns`.
*/
column: ColumnFilterState;
/**
* Added to the wrapper's own classes.
*/
className?: string;
};

interface

interface ColumnFilter<V extends unknown[] = unknown[]>

A column’s filter.

arity: number;

Number of inputs.

partial?: boolean;

Whether the filter takes effect with some of its inputs empty, like a date range open at one end. Then toCondition is called once any input is filled, with null for the empty ones; otherwise only once every input is.

key?: boolean;

Whether the filter picks the related row of a relation column, and so filters its foreign key ("ownerId") rather than its first name field ("owner.login"). For a relation whose foreign key is one field.

toCondition(target: FieldReference, values: V): FilterExpression | null;

Builds the term from the input values, null for “no filter”. Only called once every input is filled, or with partial once any is.

Parameters

target what the column filters: a field node, or the expression of a computed column, either with the builder methods, target.between(from, to)
values one value per input
fromCondition?(target: FieldReference, term: FilterExpression): V | null;

Recognizes a term as one this filter produced and returns the input values it came from, null for “not mine”. A filter without it can’t take over a term set from outside the owner, which then shows as unclaimed. The target is recognized by structure, conditionsEqual(operand, target), which holds for a field and an expression alike.

Parameters

target what the column filters, as toCondition gets it
term a term of the owner’s part of the condition
Input?: ComponentType<ColumnFilterInputProps<V>>;

Input component, where the default text inputs won’t do.

interface

interface ColumnFilterInputProps<V extends unknown[] = unknown[]>

What a filter input component gets.

label: string;

what the inputs filter, for their accessible names: the column’s label

arity: number;

number of inputs

values: FilterInputValues<V>;

current values, null for an empty input

setValues(values: FilterInputValues<V>): void;

replaces the values

filter: ColumnFilter<V>;

the filter the input belongs to, for an input that takes what it offers from it

type

type FilterInputValues<V extends unknown[] = unknown[]> = { [K in keyof V]: V[K] | null; };

Values of a filter’s inputs as the inputs hold them: null where an input is empty.

interface

interface FilterColumn

A filter on one field or expression, as a filter row or a search form has one per input group.

field: FieldExpression;

what the column filters: a field path, or a FilterDSL expression like field("numA").add(field("numB"))

filter: ColumnFilter<any>;

the filter

label?: string;

what the inputs’ accessible names call it, in place of the field’s heading, the i18n() of Type.field as a grid column has it, or the text form of the expression

function

declare function filterTarget(expression: FieldExpression): FieldReference;

The node a filter compares, with the builder methods: a field path becomes a field node, an expression built with the FilterDSL stays as it is, and a plain node like one parsed from JSON gets the methods on a copy.

An expression has the methods of a field, as the DSL’s builders give it, and its type says FieldReference for that.

Parameters

expression field path or expression

function

declare function filterLabel(column: FilterColumn): string;

The label of a filter column: the one it states, or else its field path, or a text form of its expression, add(numA, numB).

Parameters

column the filter column

function

declare function operatorFilter(name: FieldConditionName, scalarType?: string): ColumnFilter<string[]>;

A filter applying one field condition: operatorFilter("containsIgnoreCase") filters the column’s field for the text typed, operatorFilter("between", "Int") takes two numbers. It has one input per operand besides the field, and it reads back exactly the terms it writes: that condition on that field, with values as operands. On a computed column it applies the condition to the column’s expression instead, between on a sum.

The inputs are text. The filter converts it to the scalar type: a number for Int, Short, Byte and Float, a boolean for “true” and “false” with Boolean, the text as is for everything else. Text that isn’t a value of the type filters nothing, so a half-typed number doesn’t send a query.

Parameters

name field condition, with at least one operand
scalarType scalar type of the field. Default “String”.

function

declare function booleanFilter(): ColumnFilter<string[]>;

The filter of a Boolean column: eq on the field, chosen from a select of any, yes and no.

function

declare function dateRangeFilter(scalarType: "Date" | "Timestamp", options?: DateRangeFilterOptions): ColumnFilter<(string | null)[]>;

The filter of a date or time column: two dates, from and until, both included. Either can stay empty for a range open at that end. The inputs hold ISO dates, "2026-09-28", which is what a date input gives.

A Date column is compared with the dates as they are: between, or ge or le alone. A Timestamp column is filtered by the days in a time zone, the user’s unless the options name one: from the start of the first day, ge, to before the start of the day after the last, lt, combined with and(). It recognizes only terms on day starts in that zone, so a term written with other instants shows as unclaimed rather than moved to the nearest day.

It is the default filter of Date and Timestamp columns.

Parameters

scalarType “Date” or “Timestamp”
options time zone of a Timestamp column’s days

interface

interface DateRangeFilterOptions

Options for the dateRangeFilter.

timeZone?: string;

Time zone whose days a Timestamp column is filtered by. Default: the user’s, Temporal.Now.timeZoneId().

function

declare function numberContainsFilter(): ColumnFilter<string[]>;

A filter for the digits of a number: numberContainsFilter() on an order number finds 12345 for “234”. The term is contains on the field as text, so it works for any scalar with a text form.

function

declare function patternFilter(scalarType?: string): ColumnFilter<string[]>;

A filter for a search pattern:

  • * stands for any text, and a word with it matches the whole value: Foo* starts with “Foo”, *#1 ends with “#1”. A word without it matches anywhere in the value.
  • & between words: all of them match. | between groups of those: one of the groups matches. & binds tighter, and there are no brackets.
  • ! before a word: the word does not match.

Case doesn’t matter. Empty words are left out, so a pattern being typed filters by the words already there: foo & filters for “foo”.

A plain word becomes containsIgnoreCase, a word with wildcards a regular expression (likeRegex) on the lowercased value, and the words of a group are combined with and(), the groups with or(). A field that isn’t a String is matched as text.

Parameters

scalarType scalar type of the field. Default “String”.

function

declare function pick<R = any>(doc: CatalogDocument<R>, options?: PickOptions<R>): ColumnFilter<string[]>;

A filter choosing one row of a catalog: a select of the rows of a query document the view injected, filtering the foreign key that points at them.

const owners = useInjection(Q_OwnerCatalog, {config: {pageSize: 1000}});
<DataGrid doc={ foos } columns={ ["name", {field: "owner", filter: pick(owners)}] }/>

On a relation column it filters the foreign key ("ownerId", see ColumnFilter.key); in a search form, give it the foreign key as the field. The term is eq on that field with the key of the chosen row, which is its primary key from the uniqueKeys meta, a single field. A term set from outside shows as the row it names, or as the key itself where the catalog doesn’t hold that row.

The catalog is the document as it is: all of it for a small catalog, as much as its page holds otherwise. A table too large to load whole needs a picker with its own search.

Parameters

doc the catalog’s query document
options how an option is labeled

interface

interface CatalogDocument<R = any>

What pick() needs of a query document: its type and its rows. Any snapshot is one.

type: string;
rows: readonly R[];

interface

interface PickOptions<R = any>

Options for the pick() filter helper.

label?(row: R): string;

The text an option shows for a row. Default: the row’s name fields, most significant first, from the nameFields meta of its type.

function

declare function flagSetFilter(flags: readonly Flag[]): ColumnFilter<string[][]>;

A filter of flags to check: each flag stands for a term, and the rows shown are those matching every flag checked. The input is a checkbox per flag, and its value the names of the checked flags.

const STATE = flagSetFilter([
{name: "open", label: "Open", term: target => target.isNull()},
{name: "mine", label: "Mine", term: () => field("ownerId").eq(value(me))}
]);

A flag’s term is up to it: it can test what the column filters, or any other field. The terms of the checked flags are combined with and(), in the order of the flags, and a term set from outside is recognized when it is such a combination.

Parameters

flags the flags, in the order they show

interface

interface Flag

One flag of a flag set filter.

name: string;

identifies the flag among the set’s, and is what the input values hold

label: ReactNode;

what the checkbox says

term(target: FieldReference): FilterExpression;

The term the flag stands for.

Parameters

target what the column filters: a field node, or the expression of a computed column, either with the builder methods

function

declare function filled(values: readonly unknown[]): boolean;

True if every input of a filter has a value. An empty string counts as no value.

function

declare function ready(filter: ColumnFilter<any>, values: readonly unknown[]): boolean;

True if a filter’s inputs hold enough for it to take effect: all of them, or with partial any of them.

Parameters

filter the filter
values its input values

function

declare function claimTerms(part: FilterExpression | null, columns: readonly FilterColumn[], written?: readonly (readonly (FilterExpression | null)[])[]): ClaimedTerms;

Hands the terms of an owner’s part of a condition to the columns that produced them, by asking each column’s filter to recognize them (fromCondition).

The part is one term per active column, combined with and(), which unwraps a single operand. So a column term that is itself an and looks like several terms when it is the only one active, and the part is read in two steps: the whole part is offered to every column first, and if exactly one claims it, that column is the only one active. Otherwise the operands of an and are offered one by one.

A term no column claims is unclaimed, and so is a term two columns claim, which is a configuration error warned about in development: two columns filtering one field with one operator. A column claims one term at most; a second one it recognizes is unclaimed.

A term a column wrote itself is that column’s without asking its filter, which is how a filter without fromCondition keeps its own terms. Its values are null then: the owner still has what it wrote them from.

Parameters

part the owner’s part of the condition, as from ownedPart()
columns the owner’s filter columns
written per column, terms it wrote itself

interface

interface ClaimedTerms

How the terms of an owner’s part of a condition are distributed over its filter columns.

terms: (FilterExpression | null)[];

per column, the term it claimed, or null

values: (unknown[] | null)[];

per column, the input values its term came from, or null, also for a term the column wrote itself

unclaimed: FilterExpression[];

terms no column claimed, or more than one did