# Filter Overview

**Category:** Features/Filter

##  

### Overview

To better understand the use and configuration of filters in Wix Patterns and especially in [Tabs Filter](./?path=/story/features-filter-components--tabsfilter), [Toolbar Filters](./?path=/story/features-filter-components--toolbarfilters) and [Views](./?path=/story/features-display-views--views) it is important to know the difference between the [Filter Components](./?path=/story/features-filter-components) and the `Filter` object including all the built-in factories Wix Patterns provides.

The filter components as the name suggests are just wrappers over [@wix/design-system](https://www.docs.wixdesignsystem.com/?path=/story/getting-started--about) components to handle the filter object, whereas the filter object is a data structure that stores the current state of the filter that being used.

Looking to add filters to Data Extensions? [Learn more](./?path=/story/features-enrich-data-extension--data-extension#Filters_&_Sorting).


### Filter Factories

There are several different factories that can be divided into two main categories: common and uncommon. While both categories answer different requirements, usually the options available within the common category are sufficient to meet the needs for most use-cases.

Common:

- [stringsArrayFilter](./?path=/story/features-filter-factories--stringsarrayfilter) - plain strings selection
- [idNameArrayFilter](./?path=/story/features-filter-factories--idnamearrayfilter) - object selection
- [stringFilter](./?path=/story/features-filter-factories--stringfilter) - single string selection
- [dateRangeFilter](./?path=/story/features-filter-factories--daterangefilter) - specifically for [DateRangeFilter](./?path=/story/common-filters--daterangefilter) component

Uncommon:

- [arrayFilter](./?path=/story/features-filter-factories--arrayfilter)
- idArrayFilter
- rangeFilter
- idNameArrayFilterNum
- [customFilter](./?path=/story/features-filter-factories--customfilter)


### Filter Components

Wix Patterns offers a variety of plug & play components for filters, including auto-complete date ranges and more. Here are all the filter components that Wix Patterns provides out-of-the-box:

- [MultiInlineCheckboxFilter](./?path=/story/features-filter-components--multiinlinecheckboxfilter)
- [MultiSelectCheckboxFilter](./?path=/story/features-filter-components--multiselectcheckboxfilter)
- [MultiSelectCollectionFilter](./?path=/story/features-filter-components--multiselectcollectionfilter)
- [SingleSelectFilter](./?path=/story/features-filter-components--singleselectfilter)
- [AutoCompleteFilter](./?path=/story/features-filter-components--autocompletefilter)
- [DateRangeFilter](./?path=/story/features-filter-components--daterangefilter)
- [MultiAutoInlineCheckboxFilter](./?path=/story/features-filter-components--multiautoinlinecheckboxfilter)
- [RadioGroupFilter](./?path=/story/features-filter-components--radiogroupfilter)

The choice of which component to use depends on the desired user experience. Most of the components can be connected with a variety of filters, so the choice is ultimately up to you.


### Example

Joe, a product manager in the Contacts team, requested that we refactor the Contacts page.\
Here is the JIRA ticket with the details:

> Hi team,
>
> Please refactor the Contacts page using the Figma link below. Specifically, I'd like us to replace the old table with the Wix Patterns table component while keeping all of our existing filters, including some new ones that will help enterprise blockers.
>
> The filters that I need are:
> - Level (multiple selection)
> - Last seen with predefined options (last week, last month, 3 months ago...)
> - Subscription status (subscribed, unsubscribed)
> - Initial search value if exists from url params
>
> Here's a reminder about our Contact entity structure:
>```js
> {
>   id: string;
>   name: string;
>   level: string;
>   subscription?: string;
>   lastSeen?: Date;
> }
>```
>
> I'm not sure about the UI for some of the filters yet, so if it's not too much trouble, could you create two versions for the contact level and last seen - one using a dropdown and the other using a checkbox group?
>
> Thanks a lot  Joe, Product Manager@Contacts

In this example, we will only be discussing filters. If you need information on creating an initial Wix Patterns table, please refer to the [Table](./?path=/story/base-components-collections-table-table--table) documentation. You have one final opportunity to get some coffee before we begin implementing the ticket ;) Ready? good! Let's go step by step and analyze what Joe asked us to do and which component and filter types will be the most suitable for the job.

##### Contact Level

Let's assume we need to support this filter for english only, therefore the contact level value is the only thing that is needed for displaying to the user and constructing the filter for the server, it is sufficient to use a simple array of strings as our filter. This is exactly the job for [stringsArrayFilter](), a simple filter to handle selection of one or more plain strings.

```js
import React from 'react';
import { stringsArrayFilter, useStaticListFilterCollection } from '@wix/wix-patterns';

const [filter] = React.useState(() => stringsArrayFilter());

const levelsCollection = useStaticListFilterCollection(filter, [
  'Beginner',
  'Amateur',
  'Semi-Pro',
  'Professional',
  'World Class',
  'Legendary',
  'Ultimate'
]);


```

![image](https://user-images.githubusercontent.com/32840800/209715656-7737593c-b98f-4bc8-a9bb-4325f04ee603.png)

This is the most basic example for using [stringsArrayFilter](./?path=/story/features-filter-factories--stringsarrayfilter) and [MultiSelectCheckboxFilter](./?path=/story/features-filter-components--multiselectcheckboxfilter)

```js
const [filter] = React.useState(() => stringsArrayFilter());

const collection = useFilterCollection(filter, {
  queryName: 'query-contact-levels',
  fetchData: (query) => {
    const { limit, offset } = query;
    return httpClient
      .request(queryContactLevels())
      .then(({ data: { items, total } }) => ({ items, total }));
  },
  limit: 50
});

 ({
    title: item.level,
  })}
/>
```
##### Last Seen

One option for filtering a date range is to use the [DateRangeFilter](./?path=/story/features-filter-components--daterangefilter) component with [dateRangeFilter](./?path=/story/features-filter-factories--daterangefilter) provided by Wix Patterns. This duo can simplify the process of creating options for filtering a date range. However, it is also possible to create the options manually using a more basic filter like [idNameArrayFilter](./?path=/story/features-filter-factories--idnamearrayfilter) if you really want to.

```js
const [filter] = React.useState(() => dateRangeFilter());


```

![image](https://user-images.githubusercontent.com/32840800/209724537-c2330fc5-2abb-4798-aa89-d289f170af4c.png)

##### Subscription Status

The subscription status is a simple enum in server but while showing the possible options for the user we cannot use the values as-is, certainly for other languages. To handle this we will use [idNameArrayFilter](./?path=/story/features-filter-factories--idnamearrayfilter), the most common filter factory we have.

```js
import React from 'react';
import { idNameArrayFilter, useStaticListFilterCollection } from '@wix/wix-patterns';
import { useTranslation } from 'react-i18next';

const { t } = useTranslation();

const [filter] = React.useState(() => idNameArrayFilter());

const subscriptionStatusCollection = useStaticListFilterCollection(filter, [
  { id: Subscription.SUBSCRIBED, name: t('contact.subscriptionStatus.SUBSCRIBED') },
  { id: Subscription.UNSUBSCRIBED, name: t('contact.subscriptionStatus.UNSUBSCRIBED') }
]);


```

![image](https://user-images.githubusercontent.com/32840800/209724490-e205019d-b990-415c-a61c-3b50aedea5cc.png)

At first, this example may appear straightforward but how does the dropdown menu determine the value of each option? In addition, if the contact has a boolean indication for subscription status rather than an enum, assigning the `id` to a boolean value could potentially be problematic (`[{id: true, name: 'Subscribed'}, {id: false, name: 'Unsubscribed'}]`).

The [idNameArrayFilter](./?path=/story/features-filter-factories--idnamearrayfilter) filter expects `id` and `name` while `name` is basically the value of the filter. That is why we are able to see the options values without having to explicitly implement them. We could achieve the same result by using the `renderItem` property:

```js
import React from 'react';
import { idNameArrayFilter, useStaticListFilterCollection } from '@wix/wix-patterns';
import { useTranslation } from 'react-i18next';

const { t } = useTranslation();

const [filter] = React.useState(() => idNameArrayFilter());

const subscriptionStatusCollection = useStaticListFilterCollection(filter, [
  { id: Subscription.SUBSCRIBED, name: t('contact.subscriptionStatus.SUBSCRIBED') },
  { id: Subscription.UNSUBSCRIBED, name: t('contact.subscriptionStatus.UNSUBSCRIBED') }
]);

 ({title: item.foo, label: item.foo})}
/>
```

With this information in mind, we can include extra properties in the object and have access to them whenever we need (for example when constructing the filters object inside `fetchData` method), while still using the Wix Patterns provided filters. Here is another example where the outcome is the same but when the subscription status is represented as a boolean value instead of an enum:

```js
import React from 'react';
import { idNameArrayFilter, useStaticListFilterCollection } from '@wix/wix-patterns';
import { useTranslation } from 'react-i18next';

const { t } = useTranslation();

const [filter] = React.useState(() => idNameArrayFilter());

const subscriptionStatusCollection = useStaticListFilterCollection(filter, [
  { id: Subscription.SUBSCRIBED, name: t('contact.subscriptionStatus.SUBSCRIBED'), isSubscribed: true },
  { id: Subscription.UNSUBSCRIBED, name: t('contact.subscriptionStatus.UNSUBSCRIBED'), isSubscribed: false }
]);


```

While both the `id` and `name` properties exist and `name` will be used as the filter value, we have also included `isSubscribed` property to be passed with the other properties. For example, during the fetchData process, after the user selects the `Subscribed` filter option, the implementation might look like this:

```js
const fetchData = (query: OffsetQuery) => {
  const { limit, offset, search, filters } = query;
  console.log(filters.subscription); // [{id: '1', name: 'Subscribed', isSubscribed: true}]
  const isSubscribed = filters.subscription[0]?.isSubscribed
  return httpClient.request(queryContacts({limit, offset, search, filters: { isSubscribed }}))
}
```

##### Search

The search parameter is not technically part of the filters panel, but it can be initialized using [stringFilter](./?path=/story/features-filter-factories--stringfilter). This filter is usually opt-in by default, but if you want to pass an initial value for the search input, you can do so by passing your own filter to it.

```js
const collection = useCollection({
  queryName: 'query-contacts',
  search: stringFilter({initialValue: searchFromParams}),
  // ...
})
```

Using the knowledge we gained from going through the necessary filters, let's apply it to the [Tabs Filter]() feature to create a Wix Patterns [`Table`](./?path=/story/base-components-collections-table-table--table) that includes a filters button featuring all of the previously mentioned filters.



### Interactive Example

```tsx
import { Avatar, Badge } from '@wix/design-system';
import React from 'react';
/* eslint-disable import/no-extraneous-dependencies */
import { CollectionPage } from '@wix/patterns/page';
import {
  CollectionToolbarFilters,
  idNameArrayFilter,
  dateRangeFilter,
  Table,
  MultiSelectCheckboxFilter,
  stringsArrayFilter,
  stringFilter,
  useTableCollection,
  OffsetQuery,
  CollectionEmptyState,
  Filter,
  RangeItem,
  DateRangeFilter,
  SingleSelectFilter,
  MultiInlineCheckboxFilter,
  useStaticListFilterCollection,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

type ContactsFilters = {
  level: Filter<string[]>;
  lastActivity: Filter<RangeItem<Date>>;
  locale: Filter<{ id: string; name: string }[]>;
};

function ContactsTable() {

  const t = (key: string) => key.split('.').pop() || '';

  const fromSearchParams = 'jo';

  const state = useTableCollection<contacts.Contact, ContactsFilters>({
    search: stringFilter({ initialValue: fromSearchParams }),
    queryName: 'contacts-Table',
    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.level) {
        queryBuilder = queryBuilder.in('info.jobTitle', filters.level);
      }

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

      if (filters.locale) {
        queryBuilder = queryBuilder.in(
          'info.locale',
          filters.locale.map(({ id }) => id),
        );
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error Fetching Contacts',
    filters: {
      level: stringsArrayFilter(),
      lastActivity: dateRangeFilter(),
      locale: idNameArrayFilter(),
    },
  });
  const collection = state.collection;
  const localeCollection = useStaticListFilterCollection(
    collection.filters.locale,
    [
      {
        id: 'en-US',
        name: t('contacts.info.locale.en-US'),
      },
      {
        id: 'he-IL',
        name: t('contacts.info.locale.he-IL'),
      },
      {
        id: 'fr-FR',
        name: t('contacts.info.locale.fr-FR'),
      },
      {
        id: 'es-ES',
        name: t('contacts.info.locale.es-ES'),
      },
      {
        id: 'de-DE',
        name: t('contacts.info.locale.de-DE'),
      },
    ],
  );

  const levelsCollection = useStaticListFilterCollection(
    collection.filters.level,
    [
      'Beginner',
      'Amateur',
      'Semi-Pro',
      'Professional',
      'World Class',
      'Legendary',
      'Ultimate',
    ],
  );

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Table
          emptyState={<CollectionEmptyState />}
          state={state}
          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: 'Level',
              render: (contact) => contact.info?.jobTitle,
            },
            {
              title: 'Last Activity',
              render: (contact) =>
                contact.lastActivity?.activityDate?.toLocaleString(),
            },
            {
              title: 'Locale',
              render: (contact) => (
                <Badge
                  skin={
                    contact.info?.locale === 'en-US' ? 'premium' : 'neutral'
                  }
                >
                  {t(`contacts.source.sourceType.${contact.info?.locale}`)}
                </Badge>
              ),
            },
          ]}
          filters={
            <CollectionToolbarFilters
              panelTitle="Filter your contacts"
              useNewFilters
            >
              <MultiSelectCheckboxFilter
                popoverProps={{ appendTo: 'window' }}
                accordionItemProps={{ label: 'Level (Dropdown)' }}
                filter={collection.filters.level}
                collection={levelsCollection}
                renderItem={(level) => ({
                  title: level,
                  name: level,
                })}
              />
              <MultiInlineCheckboxFilter
                popoverProps={{ appendTo: 'window' }}
                accordionItemProps={{ label: 'Level (CheckboxGroup)' }}
                filter={collection.filters.level}
                data={[
                  'Beginner',
                  'Amateur',
                  'Semi-Pro',
                  'Professional',
                  'World Class',
                  'Legendary',
                  'Ultimate',
                ]}
                renderItem={(level, idx) => ({ id: `${idx}`, name: level })}
              />
              <DateRangeFilter
                filter={collection.filters.lastActivity}
                accordionItemProps={{ label: 'Last Activity' }}
              />
              <SingleSelectFilter
                popoverProps={{ appendTo: 'window' }}
                filter={collection.filters.locale}
                accordionItemProps={{ label: 'Locale' }}
                collection={localeCollection}
              />
            </CollectionToolbarFilters>
          }
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

