# Grid Sections

**Category:** Features/Display/Grid Sections

## Design

### Description

Group [`Grid`](./?path=/story/base-components-collections-grid-grid--grid) data into sections.

#### Usage

1. Create a [`Grid`](./?path=/story/base-components-collections-grid-grid--grid) collection component.
1. In the `fetchData` function of the [`useGridCollection()`](./?path=/story/base-components-collections-grid-usegridcollection--usegridcollection) hook, return data grouped by the desired field for creating sections. You can use `sort` for this, or simply return the data grouped by the field in a flat array. Patterns detects this field and sections the grid data by its values.
1. Define the `sections` prop on the `Grid`. The prop must be a [`GridSectionsProp`](./?path=/story/common-types--gridsectionsprop) object, where the `renderSection` callback returns a [`Section`](./?path=/story/common-types--collectionsection) object with `id`, `title`, and optional `primaryAction` properties.


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

### Demo

This example sorts and groups the data by `jobTitle`.

```tsx
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import { Grid, GridSections, useGridCollection } from '@wix/patterns';
import { contacts } from '@wix/crm';
import { AddSmall } from '@wix/wix-ui-icons-common';

function GridSectionsExample() {
  const groupBy = React.useCallback(
    (item: contacts.Contact) => item.info?.jobTitle as string,
    [],
  );

  const renderSection = React.useCallback(
    (sectionId: string) => ({
      title: sectionId,
      primaryAction: {
        id: 'primary-action',
        label: 'Primary Action',
        prefixIcon: <AddSmall />,
        onClick: () => {},
      },
    }),
    [],
  );

  const grid = useGridCollection<contacts.Contact>({
    queryName: 'GridSections',
    paginationMode: 'offset',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchData: (query) => {
      const { limit, offset, search } = query;

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

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

      queryBuilder.ascending('info.jobTitle');

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    fetchErrorMessage: ({ err }) => String(err),
  });

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Grid
          sections={{
            GridSections,
            groupBy,
            renderSection,
          }}
          state={grid}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

### With Badge

This example shows how to use a badge in the section header.

```tsx
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import { Grid, GridSections, Section, useGridCollection } from '@wix/patterns';
import { contacts } from '@wix/crm';
import { AddSmall } from '@wix/wix-ui-icons-common';

function GridSectionsExample() {
  const groupBy = React.useCallback((item: contacts.Contact) => {
    const name = item.info?.name?.first;
    const jobTitle = item.info?.jobTitle;
    return `${jobTitle} - ${name?.slice(0, 1).toUpperCase() || 'other'}`;
  }, []);

  const renderSection = React.useCallback((sectionId: string) => {
    const firstLetter = sectionId.split(' - ')[1]?.toLowerCase();

    return {
      title: sectionId,
      primaryAction: {
        id: 'primary-action',
        label: 'Primary Action',
        prefixIcon: <AddSmall />,
        onClick: () => {},
      },
      badge: {
        visible: true,
        skin: firstLetter === 'a' ? 'danger' : 'light',
      } as Section['badge'],
    };
  }, []);

  const grid = useGridCollection<contacts.Contact>({
    queryName: 'GridSections',
    paginationMode: 'offset',
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchData: (query) => {
      const { limit, offset, search } = query;

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

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

      queryBuilder.ascending('info.jobTitle');
      queryBuilder.ascending('info.name.first');

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    fetchErrorMessage: ({ err }) => String(err),
  });

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <Grid
          sections={{
            GridSections,
            groupBy,
            renderSection,
          }}
          state={grid}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

