# customFilter

**Category:** Features/Filter/Factories

## Design

### Usage

A generic filter factory to be passed to [useCollection](./?path=/story/common-hooks--usecollection) `filters` property. This factory, allows creating custom filters that are not provided by Wix Patterns.

To create a custom filter, we must implement 2 different parts:

- A filter state - using `customFilter` factory (see example below).
- A filter component - our component must accept a `filter` prop and be wrapped with `observer` (see example below).


**Do:**

- Use it only when you must create a custom filter component

- Use it after consulting with WixPatterns team


**Don't:**

- Use it without updating WixPatterns team


```tsx
import { customFilter } from '@wix/patterns';
```

### Custom date filter component

An example of creating a custom date filter component, though in practice it is recommended to use the pre-built [DateRangeFilter](./?path=/story/features-filter-components--daterangefilter) component instead.

```tsx
import { Avatar, DatePicker } from '@wix/design-system';
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import {
  CollectionToolbarFilters,
  customFilter,
  Table,
  observer,
  useTableCollection,
  Filter,
  OffsetQuery,
} from '@wix/patterns';
import { contacts } from '@wix/crm';
import { addDays } from 'date-fns';

type ContactsFilters = {
  lastActivity: Filter<Date>;
};

function customFilterExample() {
  // Part 1 - Implement the filter state.
  const [lastActivityDate] = React.useState(() =>
    customFilter<Date | null>({
      defaultValue: null,
      isEmpty: (date) => date == null,
      toArray: (date) => [date],
      itemKey: (date) => date?.toISOString() ?? '',
      itemName: (date) => date?.toLocaleDateString() ?? '',
      toQueryString: (date) => date?.toISOString() ?? '',
      fromQueryString: (str) => (str ? new window.Date(str) : null),
      encode: (date) => date?.toISOString(),
      decode: (str) => (typeof str === 'string' ? new Date(str) : null),
    }),
  );

  // Part 2 - Implement the filter component.
  const [CustomDateFilter] = React.useState(() =>
    // Our custom component must accept a `filter` prop and be wrapped with `observer`
    observer(function DateFilter({ filter }: { filter: Filter<Date> }) {
      return (
        <DatePicker
          popoverProps={{ appendTo: 'window' }}
          value={filter.value}
          onChange={(date: Date) => filter.refresh(date)}
        />
      );
    }),
  );

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    queryName: 'CustomFilterExample',
    fetchData: (query: OffsetQuery<ContactsFilters>) => {
      const { limit, offset, search, filters } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      if (filters.lastActivity) {
        queryBuilder = queryBuilder
          .gt('lastActivity.activityDate', filters.lastActivity)
          .lt('lastActivity.activityDate', addDays(filters.lastActivity, 1));
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {
      lastActivity: lastActivityDate as Filter<Date>,
    },
  });

  const collection = state.collection;

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          state={collection}
          columns={[
            {
              title: '',
              width: '50px',
              render: (contact) => (
                <Avatar
                  name={`${contact.info?.name?.first} ${contact.info?.name?.last}`}
                  imgProps={{ src: contact.info?.picture?.image }}
                />
              ),
            },
            {
              title: 'Name',
              width: '250px',
              render: (contact) =>
                `${contact.info?.name?.first} ${contact.info?.name?.last}`,
            },
            {
              title: 'Last Activity',
              render: (contact) =>
                contact.lastActivity?.activityDate?.toLocaleString(),
            },
          ]}
          filters={
            <CollectionToolbarFilters>
              <CustomDateFilter filter={collection.filters.lastActivity} />
            </CollectionToolbarFilters>
          }
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

## API

### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `defaultValue` | `T` | Yes | - | The value to set the value to, when clearing the filter. |
| `isEmpty` | `(value: T) => boolean` | Yes | - | Indicates whether nothing is selected.<br> For example, when implementing a range filter, when both minimum and maximum inputs are empty |
| `size` | `((value: T) => number)` | No | `A function that 0 when `isEmpty` returns true, and 1 otherwise.` | Indicates the number of effective filters.<br> For example, when implementing a range filter, 1 when only minimum/maximum are entered, 2 - when both entered. |
| `toQueryString` | `((value: T) => string)` | No | - | Convert the filter value to a string, to be stored in a URL. |
| `fromQueryString` | `((str: string) => T)` | No | - | Convert the a string value to the filter value, to be retrieved from a URL. |
| `decode` | `(value: unknown) => T` | Yes | - | Convert a unknown object to a T.<br> If the function returns null, the item will be skipped when we try to deserializate it from an array. @param value |
| `encode` | `(value: T) => unknown` | Yes | - | Return a simplified form of an item for serialization. @param value |
| `toArray` | `(value: T) => FilterItem<T>[]` | Yes | - | Flatten the correct selection to an array, to be shown in the table sub-toolbar in a tag list. Usually this will return an array with a single item, but it gives control when we want to show more than a 1 tag in the tag list. |
| `remove` | `((item: FilterItem<T>[], value: T) => T)` | No | `A function that returns `defaultValue`.` | Remove a tag from the tag list. |
| `hasDiff` | `((data: T) => boolean)` | No | - | Indicates whether current filter value is different than `data` |
| `matches` | `((item: FilterItem<T>, value: T) => boolean)` | No | - | Indicates whether an item matches the current value of the filter |
| `equals` | `((item1: FilterItem<T>, item2: FilterItem<T>) => boolean)` | No | - | A function which accepts 2 items and returns true if they are equal |
| `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` | `T` | No | - | The value to initialize the filter with (Optional) |
| `itemKey` | `(item: FilterItem<T>) => string` | Yes | - | A callback function that accepts an item and returns a unique ID of it. typically `item.id`. |
| `itemName` | `(item: FilterItem<T>) => string` | Yes | - | A callback function that accepts an item and returns the item name which is used in many messages through cairo |
| `isCustomField` | `boolean` | No | - | If the filter is a custom field |

