# operatorFilter

**Category:** Features/Filter/Factories

## Design

### Usage

The `operatorFilter` factory creates a filter that combines an operator selection with a value input. It is ideal for advanced filtering scenarios where users need to specify how they want to filter data.

This filter is particularly useful when you want to give users fine-grained control over how they search or filter data. Instead of a simple "contains" search, users can choose between exact matches, partial matches, negations, and more.

**Important: Don't use only value-less operators (`IS_EMPTY`, `NOT_EXISTS`). At least one operator requiring a value must be included.**

### Supported Operators

| Operator | WQL | Description |
|----------|-----|-------------|
| `EQUALS` | `$eq` | Equal - exact match |
| `NOT_EQUALS` | `$ne` | Not equal - exclude exact match |
| `IN` | `$in` | In - value is in the provided array |
| `NOT_IN` | `$nin` | Not in - value is not in the provided array |
| `STARTS_WITH` | `$startsWith` | Starts with - string starts with the provided value |
| `IS_EMPTY` | `$isEmpty` | Is empty - value is null or empty |
| `CONTAINS` | `$contains` | Contains - string contains the provided value |
| `NOT_CONTAINS` | `$notContains` | Not contains - string does not contain the provided value |
| `EXISTS` | `$exists` | Exists - field is not null/undefined |
| `NOT_EXISTS` | `$notExists` | Not exists - field is null/undefined |


```tsx
import { operatorFilter, FilterOperator, OperatorFilterPicker } from '@wix/patterns';
```

### string filter

A complete example showing how to use `operatorFilter` with a Table component. Select an operator from the side panel to filter the data.

```tsx
import { Avatar, Box, Heading } from '@wix/design-system';
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import {
  operatorFilter,
  FilterOperator,
  OperatorFilterPicker,
  transformOperatorFilterToWQL,
  Table,
  useTableCollection,
} from '@wix/patterns';
import { useHttpClient, searchContacts } from '../../../.storybook/http-client';

interface Contact {
  id: string;
  name: string;
  level: string;
  createdDate: string | Date;
}

function operatorFilterWithTableExample() {
  const httpClient = useHttpClient(100);

  const [nameFilter] = React.useState(() =>
    operatorFilter({
      name: 'name',
      allowedOperators: [
        FilterOperator.EQUALS,
        FilterOperator.NOT_EQUALS,
        FilterOperator.STARTS_WITH,
        FilterOperator.CONTAINS,
        FilterOperator.NOT_CONTAINS,
        FilterOperator.IS_EMPTY,
        FilterOperator.EXISTS,
        FilterOperator.NOT_EXISTS,
        FilterOperator.IN,
        FilterOperator.NOT_IN,
      ],
    }),
  );

  const state = useTableCollection<Contact>({
    queryName: 'OperatorFilterExample',
    paginationMode: 'offset',
    filters: {
      name: nameFilter,
    },
    fetchData: async ({ limit, offset, search, filters }) => {
      const nameFilters = filters.name
        ? transformOperatorFilterToWQL('name', filters.name)
        : [];

      const searchFilter = search ? [{ name: { $startsWith: search } }] : [];

      const allFilters = [...searchFilter, ...nameFilters];
      const filter = allFilters.length ? { $and: allFilters } : undefined;

      const response = (await httpClient.request(
        searchContacts({
          search: {
            filter,
            cursorPaging: { limit, offset },
          },
        }),
      )) as { data: { contacts: Contact[]; pagingMetadata: { total: number } } };

      return {
        items: response?.data?.contacts || [],
        total: response?.data?.pagingMetadata?.total,
      };
    },
    itemKey: (item) => item.id,
    itemName: (item) => item.name,
    fetchErrorMessage: () => 'Error fetching contacts',
  });

  return (
    <Box direction="horizontal" gap="SP4" height="500px">
      <Box flexGrow={1}>
        <CollectionPage height="500px">
          <CollectionPage.Header title={{ text: 'Contacts' }} />
          <CollectionPage.Content>
            <Table
              state={state}
              columns={[
                {
                  id: 'avatar',
                  title: '',
                  width: '50px',
                  render: (contact) => <Avatar name={contact.name} />,
                },
                {
                  id: 'name',
                  title: 'Name',
                  width: '200px',
                  render: (contact) => contact.name || '(empty)',
                },
                {
                  id: 'level',
                  title: 'Level',
                  width: '150px',
                  render: (contact) => contact.level,
                },
                {
                  id: 'createdDate',
                  title: 'Created',
                  render: (contact) =>
                    contact.createdDate
                      ? new window.Date(contact.createdDate).toLocaleDateString()
                      : '',
                },
              ]}
            />
          </CollectionPage.Content>
        </CollectionPage>
      </Box>
      <Box width="350px" direction="vertical" gap="SP2">
        <Heading size="small">Filter by Name</Heading>
        <OperatorFilterPicker filter={nameFilter} />
      </Box>
    </Box>
  );
}

export default operatorFilterWithTableExample;
```

## API

### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `itemKey` | `((item: OperatorFilterValue<T>) => string)` | No | - | Function to get the key of an item (for value dropdown) |
| `itemName` | `((item: OperatorFilterValue<T>) => string)` | No | - | Function to get the display name of an item (for value dropdown) |
| `equals` | `((item1: OperatorFilterValue<T>, item2: OperatorFilterValue<T>) => boolean)` | No | - | Function to check if two items are equal |
| `encode` | `((value: OperatorFilterValue<T>) => unknown)` | No | - | Function to encode the value for serialization |
| `decode` | `((value: unknown) => OperatorFilterValue<T>)` | No | - | Function to decode a value from serialization |
| `matches` | `((item: OperatorFilterValue<T>, value: OperatorFilterValue<T>) => boolean)` | No | - | Function to check if an item matches the filter value |
| `allowedOperators` | `FilterOperator[]` | Yes | - | List of allowed operators for this filter. The first operator will be used as the default. |
| `operatorOrderOverride` | `boolean` | No | - | When `true`, keeps `allowedOperators` order as-is instead of reordering by `DEFAULT_OPERATOR_ORDER`. If the first operator doesn't require a value, falls back to default order. |
| `persistent` | `boolean` | No | - | Marks the filter as a "context" or "scope" filter. This makes the filter not be visible in the sub-toolbar tag list, not saved in views, not counted as "X filters applied". |
| `initialValue` | `OperatorFilterValue<T>` | No | - | The value to initialize the filter with (Optional) |
| `isCustomField` | `boolean` | No | - | If the filter is a custom field |

