---
title: "Row Grouping - Sorting"
enterprise: true
framework: react
version: "36.1.0"
---

# Row Grouping - Sorting

This section provides details on how to configure and customise how row groups are sorted.

## Sorting Row Groups

Row Groups are [Sorted](https://www.ag-grid.com/react-data-grid/row-sorting/) by the column that they are grouped by, and use any [Custom Sorting](https://www.ag-grid.com/react-data-grid/row-sorting/#custom-sorting) configured on that column. Applying a sort to a group column generated by `groupDisplayType` will apply the sort to the row grouped columns it represents.

#### Mixed Group Sort

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  ModuleRegistry,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "country", rowGroup: true, sort: "desc" },
    { field: "year", rowGroup: true, sort: "asc" },
    { field: "athlete" },
    { field: "total" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 100,
    };
  }, []);
  const autoGroupColumnDef = useMemo<AutoGroupColumnDef>(() => {
    return {
      minWidth: 300,
    };
  }, []);

  const { data, loading } = useFetchJson<any>(
    "https://www.ag-grid.com/example-assets/olympic-winners.json",
  );

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={gridStyle}>
          <AgGridReact
            rowData={data}
            loading={loading}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
            autoGroupColumnDef={autoGroupColumnDef}
            groupDefaultExpanded={1}
          />
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Mixed Group Sort](https://www.ag-grid.com/examples/grouping-sorting/mixed-group-sort/reactFunctionalTs)

The example above demonstrates that sorting the `country` and `year` columns will sort the row groups, and clicking to sort the `Group` column applies sorting to the `country` and `year` columns.

> **Note**
>
> When using `groupDisplayType` with a [Single Group Column](https://www.ag-grid.com/react-data-grid/grouping-single-group-column/) and the columns with row grouping applied have different sort directions, the group column will instead display the mixed sort icon.

```jsx
const [columnDefs, setColumnDefs] = useState([
    { field: 'country', rowGroup: true, sort: 'desc' },
    { field: 'year', rowGroup: true, sort: 'asc' },
    // ...other column definitions
]);
const groupDisplayType = 'singleColumn';

<AgGridReact
    columnDefs={columnDefs}
    groupDisplayType={groupDisplayType}
/>
```

## Custom Row Group Sorting

The generated Group Columns can be unlinked from the columns with row grouping by configuring [Custom Group Sorting](https://www.ag-grid.com/react-data-grid/row-sorting/#custom-sorting) using a `autoGroupColumnDef.comparator`. This allows custom sorting to be applied across all levels of row grouping.

The example below demonstrates a configuration that ignores the data entirely, sorting rows by the number of descendants instead:

```jsx
const [columnDefs, setColumnDefs] = useState([
    { field: 'country', rowGroup: true },
    { field: 'year', rowGroup: true },
    // ...other column definitions
]);
const autoGroupColumnDef = useMemo(() => { 
	return {
        comparator: (valueA, valueB, nodeA, nodeB) => {
            return nodeA.allLeafChildren.length - nodeB.allLeafChildren.length;
        },
    };
}, []);

<AgGridReact
    columnDefs={columnDefs}
    autoGroupColumnDef={autoGroupColumnDef}
/>
```

#### Custom Group Sort

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  ModuleRegistry,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "country", rowGroup: true },
    { field: "year", rowGroup: true },
    { field: "athlete" },
    { field: "total" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 100,
    };
  }, []);
  const autoGroupColumnDef = useMemo<AutoGroupColumnDef>(() => {
    return {
      minWidth: 300,
      comparator: (valueA, valueB, nodeA, nodeB) =>
        (nodeA.allLeafChildren?.length ?? 0) -
        (nodeB.allLeafChildren?.length ?? 0),
    };
  }, []);

  const { data, loading } = useFetchJson<any>(
    "https://www.ag-grid.com/example-assets/olympic-winners.json",
  );

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={gridStyle}>
          <AgGridReact
            rowData={data}
            loading={loading}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
            autoGroupColumnDef={autoGroupColumnDef}
            groupDefaultExpanded={1}
          />
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Custom Group Sort](https://www.ag-grid.com/examples/grouping-sorting/custom-group-sort/reactFunctionalTs)

> **Note**
>
> When using custom group sorting, sorting the `Group` column no longer impacts the columns with row grouping, and vice versa.

## Maintain Group Order

By default, sorting on a non-group column reorders groups based on the sort. To keep groups in their structural order while only sorting the rows within each group, enable `groupMaintainOrder`:

```jsx
const [columnDefs, setColumnDefs] = useState([
    { field: 'country', rowGroup: true, hide: true },
    { field: 'athlete' },
]);
const groupMaintainOrder = true;

<AgGridReact
    columnDefs={columnDefs}
    groupMaintainOrder={groupMaintainOrder}
/>
```

With `groupMaintainOrder=true`:

- Sorting a leaf column sorts the rows inside each group; groups stay in structural order.
- Filter changes preserve group order.
- Transactions add new groups at their structural position: the end of the data-insertion order, or the position produced by `initialGroupOrderComparator` if one is configured.
- With multi-level row grouping, the order is maintained per level. Sorting a group column at one level only re-orders that level's groups; sibling levels keep their structural order. For example, with `country` and `year` grouping, sorting `year` re-orders year groups within each country, while country groups remain in their structural slot.

#### Maintain Group Order

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  ModuleRegistry,
  RowGroupingDisplayType,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "country", rowGroup: true, hide: true },
    { field: "year", rowGroup: true, hide: true },
    { field: "athlete" },
    { field: "total" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 100,
    };
  }, []);
  const autoGroupColumnDef = useMemo<AutoGroupColumnDef>(() => {
    return {
      minWidth: 200,
    };
  }, []);

  const { data, loading } = useFetchJson<any>(
    "https://www.ag-grid.com/example-assets/olympic-winners.json",
  );

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={gridStyle}>
          <AgGridReact
            rowData={data}
            loading={loading}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
            autoGroupColumnDef={autoGroupColumnDef}
            groupDisplayType={"multipleColumns"}
            groupMaintainOrder={true}
            groupDefaultExpanded={1}
          />
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Maintain Group Order](https://www.ag-grid.com/examples/grouping-sorting/maintain-group-order/reactFunctionalTs)

The example above uses `groupDisplayType: 'multipleColumns'` so each group level has its own header. Try the following to see per-level isolation:

- Sort `Athlete` or `Total` (leaf columns): only the rows inside each year group reorder; year and country groups keep their structural order.
- Sort the `Year` group column: only year groups within each country reorder; country groups stay structural.
- Sort the `Country` group column: only country groups reorder; year groups within each country keep their structural order.
- Clear a group-column sort: that level reverts to structural order; sibling levels are unaffected.

The structural order is the data-insertion order by default, or the order produced by [`initialGroupOrderComparator`](#unsorted-group-order) if one is configured. If a group column had a sort applied and the user later explicitly clears that sort, the structural order is restored.

## Unsorted Group Order

When no sorting is applied, the groups are ordered by the order in which they appear in the data. This order can be overwritten with a custom initial order by providing an `initialGroupOrderComparator` grid option.

> **Note**
>
> As this is an initial order of groups, it executes before filtering and aggregation. This means it cannot use post-filtered data, or aggregated values as comparison criteria.

#### Initial Group Order

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  InitialGroupOrderComparator,
  InitialGroupOrderComparatorParams,
  ModuleRegistry,
  RowGroupingDisplayType,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import { useFetchJson } from "./useFetchJson";

if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

const modules = [
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "country", rowGroup: true, hide: true },
    { field: "year", rowGroup: true, hide: true },
    { field: "athlete" },
    { field: "total" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 100,
    };
  }, []);
  const autoGroupColumnDef = useMemo<AutoGroupColumnDef>(() => {
    return {
      minWidth: 200,
    };
  }, []);
  const initialGroupOrderComparator = useCallback(
    (params: InitialGroupOrderComparatorParams) =>
      params.nodeA.allLeafChildren.length - params.nodeB.allLeafChildren.length,
    [],
  );

  const { data, loading } = useFetchJson<any>(
    "https://www.ag-grid.com/example-assets/olympic-winners.json",
  );

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div style={gridStyle}>
          <AgGridReact
            rowData={data}
            loading={loading}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
            autoGroupColumnDef={autoGroupColumnDef}
            groupDisplayType={"multipleColumns"}
            initialGroupOrderComparator={initialGroupOrderComparator}
          />
        </div>
      </div>
    </AgGridProvider>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Initial Group Order](https://www.ag-grid.com/examples/grouping-sorting/initial-group-order/reactFunctionalTs)

The example above demonstrates the following configuration to order group rows based on the number of leaf children:

```jsx
const initialGroupOrderComparator = (params) =>
    params.nodeA.allLeafChildren.length - params.nodeB.allLeafChildren.length;

<AgGridReact initialGroupOrderComparator={initialGroupOrderComparator} />
```
