# PickerModal

**Category:** Base Components/Collections/Picker

## Design

### Description

`PickerModal` allows rendering a modal that (dynamically) renders a [SelectorList](https://www.docs.wixdesignsystem.com/?path=/story/components-lists--selectorlist) powered by Wix Patterns collection state.


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

---

### Examples

### Multi Select

```tsx
import { Avatar, EmptyState, Page } from '@wix/design-system';
import React from 'react';
import { queryContacts } from '@wix/ambassador-contacts-v4-contact/http';
import {
  OffsetQuery,
  PickerContent,
  PickerContentComponentProps,
  PickerModal,
  PickerModalOpenButton,
  stringsArrayFilter,
  usePickerContent,
  Filter,
} from '@wix/patterns';
import { usePickerModal } from '@wix/patterns/essentials';
import { useHttpClient } from '@wix/fe-essentials/react';
import { Contact } from '@wix/ambassador-contacts-v4-contact/types';

type ContactsFilters = {
  level: Filter<string[]>;
};

function PickerModalMultiSelect() {
  const httpClient = useHttpClient();

  // Usually we will extract this component to a separate file and use dynamic import when passing it as `pickerContentComponent`
  const pickerContentComponent = async () => ({
    default: function ContactsPicker(
      props: PickerContentComponentProps<Contact, ContactsFilters>,
    ) {
      const state = usePickerContent({
        fetchErrorMessage: ({ err }) => String(err),
        modalState: props.modalState,
        filters: {
          level: stringsArrayFilter(props.modalState.filters.level),
        },
        itemName: (contact) => contact.id as string,
      });

      return (
        <PickerContent
          state={state}
          showSelectAllCheckbox
          renderItem={(contact) => ({
            subtitle: contact.level,
            image: <Avatar size="size60" shape="square" name={contact.name} />,
          })}
          {...props}
        />
      );
    },
  });

  const state = usePickerModal<Contact, ContactsFilters>({
    queryName: 'PickerModalSelectAll',
    fetchData: (query: OffsetQuery<ContactsFilters>) => {
      const { limit, offset, search, filters } = query;
      return httpClient
        .request(
          queryContacts({
            query: { paging: { limit, offset }, filter: filters },
            search,
          }),
        )
        .then(({ data: { contacts, pagingMetadata } }) => ({
          items: contacts as Contact[],
          total: pagingMetadata?.total,
        }));
    },
    limit: 100,
    pickerContentComponent,
    onSelect: (_contacts) => {
      // Do something with contacts
    },
  });

  return (
    <Page>
      <Page.Header
        title="Picker Modal Example"
        actionsBar={
          <>
            <PickerModalOpenButton state={state}>
              Select Contacts
            </PickerModalOpenButton>

            <PickerModal
              title="Select Contacts"
              state={state}
              multiple
              renderError={(err) => <EmptyState title={String(err)} />}
            />
          </>
        }
      />
    </Page>
  );
}
```

---

### Single Selection

 Radio button selection, the user can select one item only

```tsx
import { Avatar, EmptyState, Page } from '@wix/design-system';
import React from 'react';
import { queryContacts } from '@wix/ambassador-contacts-v4-contact/http';
import {
  OffsetQuery,
  PickerContent,
  PickerContentComponentProps,
  PickerModal,
  PickerModalOpenButton,
  usePickerContent,
} from '@wix/patterns';
import {
  WixPatternsEssentialsProvider,
  usePickerModal,
} from '@wix/patterns/essentials';
import { useHttpClient } from '@wix/fe-essentials/react';
import { Contact } from '@wix/ambassador-contacts-v4-contact/types';

function PickerModalSingleSelectExample() {
  const httpClient = useHttpClient();

  // Usually we will extract this component to a separate file and use dynamic import when passing it as `pickerContentComponent`
  const pickerContentComponent = async () => ({
    default: function ContactsPicker(
      props: PickerContentComponentProps<Contact>,
    ) {
      const state = usePickerContent({
        modalState: props.modalState,
        fetchErrorMessage: ({ err }) => String(err),
        itemName: (contact) => contact.id as string,
      });

      return (
        // wrap with a provider that suits your environment
        <WixPatternsEssentialsProvider>
          <PickerContent
            state={state}
            renderItem={(contact) => ({
              subtitle: contact.level,
              image: (
                <Avatar size="size60" shape="square" name={contact.name} />
              ),
            })}
            renderEmptyState={() => (
              <EmptyState title="You don’t have any contacts that support this action." />
            )}
            {...props}
          />
        </WixPatternsEssentialsProvider>
      );
    },
  });

  const state = usePickerModal<Contact>({
    queryName: 'PickerModalSingleSelectExample',
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;
      return httpClient
        .request(
          queryContacts({
            query: { paging: { limit, offset }, filter: filters },
            search,
          }),
        )
        .then(({ data: { contacts, pagingMetadata } }) => ({
          items: contacts as Contact[],
          total: pagingMetadata?.total,
        }));
    },
    limit: 25,
    // Usually we will use dynamic import, i.e pickerContentComponent: () => import('./ContactsPicker')
    pickerContentComponent,
    onSelect: (_contacts) => {
      // Do something with contacts
    },
  });

  return (
    <Page>
      <Page.Header
        title="Picker Modal Example"
        actionsBar={
          <>
            <PickerModalOpenButton state={state}>
              Select a Contact
            </PickerModalOpenButton>

            <PickerModal
              title="Select a Contact"
              state={state}
              renderError={(err) => <EmptyState title={String(err)} />}
            />
          </>
        }
      />
    </Page>
  );
}
```

---

### Pre Selected Item

 When the user opens the modal some of the items are already selected

```tsx
import { Avatar, EmptyState, Page } from '@wix/design-system';
import React from 'react';
import { queryContacts } from '@wix/ambassador-contacts-v4-contact/http';
import {
  OffsetQuery,
  PickerContent,
  PickerContentComponentProps,
  PickerModal,
  PickerModalOpenButton,
  usePickerContent,
} from '@wix/patterns';
import {
  WixPatternsEssentialsProvider,
  usePickerModal,
} from '@wix/patterns/essentials';
import { useHttpClient } from '@wix/fe-essentials/react';
import { Contact } from '@wix/ambassador-contacts-v4-contact/types';

function PickerModalInitialSelect() {
  const httpClient = useHttpClient();

  const selection = React.useRef<Contact[]>([]);

  // Usually we will extract this component to a separate file and use dynamic import when passing it as `pickerContentComponent`
  const pickerContentComponent = async () => ({
    default: function ContactsPicker(
      props: PickerContentComponentProps<Contact>,
    ) {
      const state = usePickerContent({
        modalState: props.modalState,
        fetchErrorMessage: ({ err }) => String(err),
        initialSelect: {
          defaultStatus: 'unchecked',
          values: selection.current.map((value) => ({
            value,
            status: 'checked',
          })),
        },
        itemName: (contact) => contact.id as string,
      });

      return (
        // wrap with a provider that suits your environment
        <WixPatternsEssentialsProvider>
          <PickerContent
            state={state}
            renderItem={(contact) => ({
              subtitle: contact.level,
              image: (
                <Avatar size="size60" shape="square" name={contact.name} />
              ),
            })}
            renderEmptyState={() => (
              <EmptyState title="You don’t have any contacts that support this action." />
            )}
            {...props}
          />
        </WixPatternsEssentialsProvider>
      );
    },
  });

  const state = usePickerModal<Contact>({
    queryName: 'PickerModalExample',
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;
      return httpClient
        .request(
          queryContacts({
            query: { paging: { limit, offset }, filter: filters },
            search,
          }),
        )
        .then(({ data: { contacts, pagingMetadata } }) => ({
          items: contacts as Contact[],
          total: pagingMetadata?.total,
        }));
    },
    fetchInitialProps: () => {
      return httpClient
        .request(
          queryContacts({
            query: { paging: { limit: 25, offset: 0 }, filter: {} },
          }),
        )
        .then(({ data: { contacts = [] } }) => {
          selection.current = contacts.slice(3, 6);
        });
    },

    limit: 25,
    // Usually we will use dynamic import, i.e pickerContentComponent: () => import('./ContactsPicker')
    pickerContentComponent,
    onSelect: (_contacts) => {
      // Do something with contacts
    },
  });

  return (
    <Page>
      <Page.Header
        title="Picker Modal Example"
        actionsBar={
          <>
            <PickerModalOpenButton state={state}>
              Select a Contact
            </PickerModalOpenButton>

            <PickerModal
              title="Select a Contact"
              state={state}
              multiple
              renderError={(err) => <EmptyState title={String(err)} />}
            />
          </>
        }
      />
    </Page>
  );
}
```

---

### With Filter Dropdown

You can add a `SingleSelectFilter` to your Picker Modal to filter its contents. To have it you need to pass `SingleSelectFilter` to the `filterComponent` prop in `PickerContent` and define the filter behavior when creating a state (check out `usePickerContent` and `usePickerModal`) in the example.

```tsx
import { Avatar, EmptyState, Page } from '@wix/design-system';
import React from 'react';
import { queryContacts } from '@wix/ambassador-contacts-v4-contact/http';
import {
  OffsetQuery,
  PickerContent,
  PickerContentComponentProps,
  PickerModal,
  PickerModalOpenButton,
  usePickerContent,
  idNameArrayFilter,
  SingleSelectFilter,
  Filter,
  useStaticListFilterCollection,
} from '@wix/patterns';
import {
  WixPatternsEssentialsProvider,
  usePickerModal,
} from '@wix/patterns/essentials';
import { useHttpClient } from '@wix/fe-essentials/react';
import { Contact } from '@wix/ambassador-contacts-v4-contact/types';

type ContactFilters = {
  level: Filter<{ id: string; name: string }[]>;
};

function PickerModalFilterDropdownExample() {
  const httpClient = useHttpClient();

  // Usually we will extract this component to a separate file and use dynamic import when passing it as `pickerContentComponent`
  const pickerContentComponent = async () => ({
    default: function ContactsPicker(
      props: PickerContentComponentProps<Contact, ContactFilters>,
    ) {
      const state = usePickerContent({
        modalState: props.modalState,
        fetchErrorMessage: ({ err }) => String(err),
        filters: {
          level: idNameArrayFilter(props.modalState.filters.level),
        },
        itemName: (contact) => contact.id as string,
      });

      const filterTypesCollection = useStaticListFilterCollection(
        state.filters.level,
        [
          { id: '1', name: 'Beginner' },
          { id: '2', name: 'Amateur' },
          { id: '3', name: 'Semi-Pro' },
          { id: '4', name: 'Professional' },
          { id: '5', name: 'World Class' },
          { id: '6', name: 'Legendary' },
          { id: '7', name: 'Ultimate' },
          { id: '8', name: 'Ye' },
        ],
      );

      return (
        // wrap with a provider that suits your environment
        <WixPatternsEssentialsProvider>
          <PickerContent
            filterComponent={
              <SingleSelectFilter
                filter={state?.filters?.level}
                collection={filterTypesCollection}
                renderItem={(item, index) => ({
                  title: item.name,
                })}
              />
            }
            state={state}
            renderItem={(contact) => ({
              subtitle: contact.level,
              image: (
                <Avatar size="size60" shape="square" name={contact.name} />
              ),
            })}
            {...props}
          />
        </WixPatternsEssentialsProvider>
      );
    },
  });

  const state = usePickerModal<Contact, ContactFilters>({
    queryName: 'PickerModalSingleSelectExample',
    filters: {
      level: {
        initialValue: [{ id: '1', name: 'Beginner' }],
      },
    },
    fetchData: (query: OffsetQuery<ContactFilters>) => {
      const { limit, offset, search, filters } = query;
      return httpClient
        .request(
          queryContacts({
            query: {
              paging: { limit, offset },
              filter: { level: filters.level?.[0].id },
            },
            search,
          }),
        )
        .then(({ data: { contacts, pagingMetadata } }) => {
          return {
            items: contacts as Contact[],
            total: pagingMetadata?.total,
          };
        });
    },
    limit: 25,
    // Usually we will use dynamic import, i.e pickerContentComponent: () => import('./ContactsPicker')
    pickerContentComponent,
    onSelect: (_contacts) => {
      // Do something with contacts
    },
  });

  return (
    <Page>
      <Page.Header
        title="Picker Modal Example"
        actionsBar={
          <>
            <PickerModalOpenButton state={state}>
              Select a Contact
            </PickerModalOpenButton>

            <PickerModal
              title="Select a Contact"
              state={state}
              renderError={(err) => <EmptyState title={String(err)} />}
            />
          </>
        }
      />
    </Page>
  );
}
```

---

### Search Override

You can override the search behavior using the `search` prop. Pass `false` to hide search, or a custom ``

```tsx
import { Avatar, EmptyState, Page } from '@wix/design-system';
import React from 'react';
import { queryContacts } from '@wix/ambassador-contacts-v4-contact/http';
import {
  OffsetQuery,
  PickerContent,
  PickerContentComponentProps,
  PickerModal,
  PickerModalOpenButton,
  stringsArrayFilter,
  usePickerContent,
  Filter,
  CollectionSearch,
} from '@wix/patterns';
import { usePickerModal } from '@wix/patterns/essentials';
import { useHttpClient } from '@wix/fe-essentials/react';
import { Contact } from '@wix/ambassador-contacts-v4-contact/types';

type ContactsFilters = {
  level: Filter<string[]>;
};

function SearchOverride() {
  const httpClient = useHttpClient();

  // Usually we will extract this component to a separate file and use dynamic import when passing it as `pickerContentComponent`
  const pickerContentComponent = async () => ({
    default: function ContactsPicker(
      props: PickerContentComponentProps<Contact, ContactsFilters>,
    ) {
      const state = usePickerContent({
        fetchErrorMessage: ({ err }) => String(err),
        modalState: props.modalState,
        filters: {
          level: stringsArrayFilter(props.modalState.filters.level),
        },
        itemName: (contact) => contact.id as string,
      });

      return (
        <PickerContent
          state={state}
          search={<CollectionSearch placeholder="Custom placeholder..." />}
          showSelectAllCheckbox
          renderItem={(contact) => ({
            subtitle: contact.level,
            image: <Avatar size="size60" shape="square" name={contact.name} />,
          })}
          {...props}
        />
      );
    },
  });

  const state = usePickerModal<Contact, ContactsFilters>({
    queryName: 'PickerModalSelectAll',
    fetchData: (query: OffsetQuery<ContactsFilters>) => {
      const { limit, offset, search, filters } = query;
      return httpClient
        .request(
          queryContacts({
            query: { paging: { limit, offset }, filter: filters },
            search,
          }),
        )
        .then(({ data: { contacts, pagingMetadata } }) => ({
          items: contacts as Contact[],
          total: pagingMetadata?.total,
        }));
    },
    limit: 100,
    pickerContentComponent,
    onSelect: (_contacts) => {
      // Do something with contacts
    },
  });

  return (
    <Page>
      <Page.Header
        title="Picker Modal Example"
        actionsBar={
          <>
            <PickerModalOpenButton state={state}>
              Select Contacts
            </PickerModalOpenButton>

            <PickerModal
              title="Select Contacts"
              state={state}
              multiple
              renderError={(err) => <EmptyState title={String(err)} />}
            />
          </>
        }
      />
    </Page>
  );
}
```

## API

### API

- `Lazy Loading` - `PickerModal` is designed not to bundle `Wix Patterns` code in the original application that wants to open the modal.
Instead, it loads it dynamically when the modal actually needs to be open.
To allow this feature to work, we split the modal integration to 2 components - a lite modal component, and a "heavy" (dynamically imported) picker component. For pre-fetching the data of the first page we ask for the `fetchData`, `paginationMode`, `filters` & `limit` options upfront (see ["With Filters" example](./?path=/story/base-components-collections-picker--pickermodal#With_filter_dropdown) below).
- Immediate call to action - when only 1 item is available on the server, the call to action (`onSelect`) will be triggered immediately without opening the modal.

#### Integration
> Make sure to import `usePickerModal` from the sub-package relevant to your environment, see import examples here: [`usePickerModal`](./?path=/story/base-components-collections-picker-hooks--usepickermodal)

Integrating with `PickerModal` is usually done by the following steps

- Writing a custom component for the modal content using [``](./?path=/story/base-components-collections-picker--pickercontent)
- Initializing a state object using [`usePickerModal`](./?path=/story/base-components-collections-picker-hooks--usepickermodal) by passing the custom modal content component as a dynamic import to the `pickerContentComponent` option
- Passing the state object to ``
- Rendering a button that will open the modal (`Wix Patterns` provides a ready-to-use button ``)



### Extends

[CustomModalLayoutProps](https://www.docs.wixdesignsystem.com/?path=/story/components-overlays-modal--custommodallayout)

### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `modalProps` | `Partial<ModalProps>` | No | - | Optional props to forward to `Modal` component |
| `state` | `PickerModalState<T, F>` | Yes | - | State object created with [`usePickerModal`](./?path=/story/base-components-collections-picker-hooks--usepickermodal) |
| `renderError` | `(params: { err: unknown; isOnline: boolean; }) => ReactElement<any, string \| JSXElementConstructor<any>>` | Yes | - | A component to be rendered in the modal content when there's a failure fetching the data @param params @param params.err - The original failure error object @param params.isOnline - Indicates whether internet connection is available or not |
| `multiple` | `MultipleSelection` | No | - | Limits the number of items that can be selected.<br> Either a boolean or a number. A number indicates the maximum number of selections, `true` indicates no limit, `false` indicates only 1 item (radio button will be shown instead of checkboxes) |
| `title` | `ReactNode` | No | - | Sets text title for the modal; can be used in conjunction with other components |
| `subtitle` | `ReactNode` | No | - | Sets text subtitle for the modal; can be used in conjunction with other components |
| `content` | `ReactNode` | No | - | Contains all modal's content in its middle area. `<CustomModalLayout/>` children are passed as `content`, too. |
| `primaryButtonText` | `ReactNode` | No | - | Sets text label or other content (e.g., <Loader/>) for the primary button |
| `primaryButtonProps` | `Omit<ButtonProps, "internalDataHook">` | No | - | Passes on any `<Button/>` properties on the primary button |
| `primaryButtonOnClick` | `(() => void)` | No | - | Defines a call-back function after user clicks on the primary button |
| `primaryButtonTooltipProps` | `(TooltipCommonProps & { content: ReactNode; })` | No | - | Passes on any `<Tooltip/>` properties on the primary button tooltip |
| `secondaryButtonText` | `ReactNode` | No | - | Sets text label for the secondary button |
| `secondaryButtonProps` | `Omit<ButtonProps, "internalDataHook">` | No | - | Passes on any `<Button/>` properties on the secondary button |
| `secondaryButtonOnClick` | `(() => void)` | No | - | Defines a call-back function after user clicks on the secondary button |
| `actionsSize` | `"small" \| "tiny" \| "medium" \| "large"` | No | - | Sets the size of action buttons |
| `sideActions` | `ReactNode` | No | - | Contains a checkbox or other components at the start of the footer |
| `footnote` | `ReactNode` | No | - | Renders supplementary text or other components at the bottom of the modal |
| `footnoteSkin` | `"light" \| "neutral"` | No | - | Sets the look and feel of the footnote |
| `width` | `Width<string \| number>` | No | - | Fixes the width of component; if content area is wider than `width`, it scrolls horizontally |
| `maxWidth` | `MaxWidth<string \| number>` | No | - | Sets the maximum width of component; if content area is longer than maxWidth, it scrolls horizontally |
| `height` | `Height<string \| number>` | No | - | Fixes the height of component |
| `maxHeight` | `MaxHeight<string \| number>` | No | - | Sets the maximum height of component; if content area is longer than maxHeight, it scrolls vertically |
| `removeContentPadding` | `boolean` | No | - | Removes 30 px left and right padding from the content area |
| `showHeaderDivider` | `boolean \| "auto"` | No | - | Controls visibility of a header divider. When set to `auto`, it is shown only when scroll position is greater than 0. When set to `true`, it is always visible, and when set to `false`, it is always hidden. |
| `showFooterDivider` | `boolean \| "auto"` | No | - | Controls visibility of a footer divider. When set to `auto`, it is shown up until content is scrolled to the very bottom. When set to `true`, it is always visible, and when set to `false`, it is always hidden. |
| `overflowY` | `"visible" \| "hidden" \| "scroll" \| "auto" \| "overlay" \| "none" \| "clip" \| "-moz-initial" \| "inherit" \| "initial" \| "revert" \| "revert-layer" \| "unset" \| "-moz-hidden-unscrollable"` | No | - | Determines what happens when content overflows component vertically. Value 'none' will be removed in next major, use "visible" value instead. |
| `style` | `CSSProperties` | No | - | Sets object of CSS styles |
| `theme` | `"standard" \| "premium" \| "destructive"` | No | - | @deprecated use skin prop instead |
| `className` | `string` | No | - | Specifies a CSS class name to be appended to the component’s root element. |
| `dataHook` | `string` | No | - | Applies a data-hook HTML attribute to be used in the tests. |
| `closeButtonProps` | `ModalCloseButtonProps` | No | - | Close button props. |
| `helpButtonProps` | `ModalControlButtonProps` | No | - | Help button props. |
| `onCloseButtonClick` | `MouseEventHandler<HTMLButtonElement>` | No | - | @deprecated use `closeButtonProps` instead. |
| `onHelpButtonClick` | `MouseEventHandler<HTMLButtonElement>` | No | - | @deprecated use `helpButtonProps` instead. |
| `skin` | `"standard" \| "premium" \| "destructive"` | No | - | a global skin for the modal, will be applied as stylable state and will affect footer buttons skin. |

## BI

### Picker Modal Events

  Event   |   Description   |
--------- | --------------- |
[144:110](https://bo.wix.com/data-tools/bi-catalog-app/event/144:110) | sent when a component starts loading
[144:111](https://bo.wix.com/data-tools/bi-catalog-app/event/144:111) | sent when a component is done loading
[144:112](https://bo.wix.com/data-tools/bi-catalog-app/event/144:112) | sent when a component is done loading more items (after scrolling)
[144:113](https://bo.wix.com/data-tools/bi-catalog-app/event/144:113) | sent when a component is dismissed
[144:114](https://bo.wix.com/data-tools/bi-catalog-app/event/144:114) | sent when a user select/deselects an item(s) in a list



### Component load events
Event   |   Description   |
--------- | --------------- |
[144:110](https://bo.wix.com/data-tools/bi-catalog-app/event/144:110) | Sent when a Wix Patterns component starts loading
[144:111](https://bo.wix.com/data-tools/bi-catalog-app/event/144:111) | Sent when a Wix Patterns component is done loading


#### 🐪 Couldn't find what you need?
* Check our [BI Catalog](https://bo.wix.com/data-tools/bi-catalog-app?viewId=all-items-view&selectedColumns=src%2Cevid%2Cname%2Cowner%2Cproduct%2CuserType+false%2CdateUpdated%2CdateCreated+false%2CcreatedBy+false%2Cstatus&source=%5B%7B%22id%22%3A%22144%22%2C%22name%22%3A%22144+-+Cairo%22%7D%5D)
* Contact us on [#cairo-bi](https://wix.slack.com/archives/C03N53KURH9)


