---
product: "AG Grid"
title: "Rich Select Cell Editor - Async Values"
description: "The Rich Select Cell Editor supports loading values asynchronously, including paged loading and server-side filtering."
enterprise: true
framework: react
version: "36.2.0"
related:
    - title: "Customisation"
      url: "https://www.ag-grid.com/archive/36.2.0/react-data-grid/provided-cell-editors-rich-select-customisation/"
llms: "https://www.ag-grid.com/archive/36.2.0/llms.txt"
---

# Rich Select Cell Editor - Async Values

The Rich Select Cell Editor supports loading values asynchronously, including paged loading and server-side filtering.

## Async Values

List values can be provided asynchronously to the editor as shown below:

When `values` is provided as a callback function, the callback receives `params` with type `RichCellEditorValuesCallbackParams`.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `values` | `TValue[] \| RichCellEditorValuesCallback` |  |  |  |

#### Rich Select Async Values

```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 {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  IRichCellEditorParams,
  ModuleRegistry,
  RichCellEditorValuesCallbackParams,
  TextEditorModule,
  enableDevValidations,
} from "ag-grid-community";
import { RichSelectModule } from "ag-grid-enterprise";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableDevValidations();
}

const modules = [TextEditorModule, ClientSideRowModelModule, RichSelectModule];

const languages = ["English", "Spanish", "French", "Portuguese", "(other)"];

function getRandomNumber(min: number, max: number) {
  // min and max included
  return Math.floor(window.agRandom() * (max - min + 1) + min);
}

const getValueFromServer: (
  _params: RichCellEditorValuesCallbackParams,
) => Promise<string[]> = (_params: RichCellEditorValuesCallbackParams) => {
  // simulates an async request to a server
  return new Promise((resolve) => {
    setTimeout(() => resolve(languages), 1000);
  });
};

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<any[]>(
    new Array(100)
      .fill(null)
      .map(() => ({ language: languages[getRandomNumber(0, 4)] })),
  );
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    {
      field: "language",
      cellEditor: "agRichSelectCellEditor",
      cellEditorParams: {
        values: getValueFromServer,
      } as IRichCellEditorParams,
    },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      width: 200,
      editable: true,
    };
  }, []);

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

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

[Live example: Rich Select Async Values](https://www.ag-grid.com/archive/36.2.0/examples/provided-cell-editors-rich-select-async/rich-select-async-values/reactFunctionalTs/)

```js
columnDefs: [
    {
        field: 'language',
        cellEditor: 'agRichSelectCellEditor',
        cellEditorParams: {
            values: (_params) => fetch('/api/languages').then((res) => res.json()),
        }
    }
]
```

## Paged Async Values

For large datasets, `valuesPage` avoids loading all items at once and instead loads values on demand based on user scroll and interactions. `valuesPageInitialStartRow` can be used to set the initial position. The `valuesPage` callback receives `params` with type `RichCellEditorValuesPageParams` and should return a value that conforms to the `RichCellEditorValuesPageResult` interface.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `valuesPage` | `RichCellEditorValuesPageCallback` |  |  |  |
| `valuesPageInitialStartRow` | `number \| RichCellEditorValuesPageStartRowCallback` |  |  |  |
| `valuesPageSize` | `number` |  |  |  |
| `valuesPageLoadThreshold` | `number` |  |  |  |

#### Rich Select Paged Async Values

```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 {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  IRichCellEditorParams,
  ModuleRegistry,
  RichCellEditorValuesPageParams,
  RichCellEditorValuesPageResult,
  TextEditorModule,
  enableDevValidations,
} from "ag-grid-community";
import { RichSelectModule } from "ag-grid-enterprise";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableDevValidations();
}

const modules = [TextEditorModule, ClientSideRowModelModule, RichSelectModule];

const languages = new Array(20000)
  .fill(null)
  .map((_, index) => `Language ${index + 1}`);

function getRandomNumber(min: number, max: number) {
  // min and max included
  return Math.floor(window.agRandom() * (max - min + 1) + min);
}

const getValuePageFromServer: (
  params: RichCellEditorValuesPageParams,
) => Promise<RichCellEditorValuesPageResult<string>> = (
  params: RichCellEditorValuesPageParams,
) => {
  // Simulates an async request to a server
  return new Promise((resolve) => {
    setTimeout(() => {
      const pageValues = languages.slice(params.startRow, params.endRow);
      const nextOffset =
        params.endRow < languages.length ? String(params.endRow) : null;
      resolve({
        values: pageValues,
        lastRow: languages.length,
        cursor: nextOffset,
      });
    }, 300);
  });
};

const getInitialStartRowForValue: (
  value: string | null | undefined,
) => number = (value: string | null | undefined) => {
  if (!value) {
    return 0;
  }
  const match = /^Language (\d+)$/.exec(value);
  if (!match) {
    return 0;
  }
  const selectedIndex = Number(match[1]) - 1;
  return Math.max(selectedIndex - 50, 0);
};

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<any[]>(
    new Array(100)
      .fill(null)
      .map(() => ({
        language: languages[getRandomNumber(0, languages.length - 1)],
      })),
  );
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    {
      field: "language",
      width: 300,
      editable: true,
      cellEditor: "agRichSelectCellEditor",
      cellEditorParams: {
        valuesPage: getValuePageFromServer,
        valuesPageInitialStartRow: (value: string | null | undefined) =>
          getInitialStartRowForValue(value),
        valuesPageSize: 100,
        valuesPageLoadThreshold: 8,
      } as IRichCellEditorParams,
    },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      width: 220,
      editable: true,
    };
  }, []);

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

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

[Live example: Rich Select Paged Async Values](https://www.ag-grid.com/archive/36.2.0/examples/provided-cell-editors-rich-select-async/rich-select-paged-async-values/reactFunctionalTs/)

```js
columnDefs: [
    {
        cellEditor: 'agRichSelectCellEditor',
        cellEditorParams: {
            valuesPage: (params) => {
                return fetch(`/api/languages?
                    startRow=${params.startRow}
                    &endRow=${params.endRow}`)
                .then((res) => res.json());
            },
            valuesPageInitialStartRow: (value) => getRowForSelectedValue(value),
            valuesPageSize: 100,
            valuesPageLoadThreshold: 10
        }
    }
]
```

## Async Filtering

For advanced filtering scenarios, combine async `values` callback, `allowTyping`, `filterList: true`, and `filterListAsync` to enable async filtering. The `values` callback params remain typed as `RichCellEditorValuesCallbackParams`.

When `filterListAsync` is set to `true`, the cell editor behaves as follows:

- It calls the `values` callback with the current search term.
- It delays the search request by 300ms (this can be adjusted using `searchDebounceDelay`) to avoid excessive calls.
- A loading indicator appears while the network request is in progress.
- Once the promise resolves, the dropdown is updated with the filtered results.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `values` | `TValue[] \| RichCellEditorValuesCallback` |  |  |  |
| `filterListAsync` | `boolean` |  |  |  |
| `searchDebounceDelay` | `number` |  |  |  |

This is shown in the example below:

#### Rich Select Async Filtering

```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 {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  IRichCellEditorParams,
  ModuleRegistry,
  RichCellEditorValuesCallbackParams,
  TextEditorModule,
  enableDevValidations,
} from "ag-grid-community";
import { RichSelectModule } from "ag-grid-enterprise";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableDevValidations();
}

const modules = [TextEditorModule, ClientSideRowModelModule, RichSelectModule];

const languages = ["English", "Spanish", "French", "Portuguese", "(other)"];

function getRandomNumber(min: number, max: number) {
  // min and max included
  return Math.floor(window.agRandom() * (max - min + 1) + min);
}

const getValueFromServer: (
  params: RichCellEditorValuesCallbackParams,
) => Promise<string[]> = (params: RichCellEditorValuesCallbackParams) => {
  const search = params.search?.toLowerCase() ?? "";
  // Simulates an async request to a server
  return new Promise((resolve) => {
    console.log(`Grid requested \`${search}\` from server.`);
    setTimeout(() => {
      const entries = languages.filter((l) => l.toLowerCase().includes(search));
      console.log(
        `Server response for \`${search}\`: ${entries.length} hit${entries.length === 1 ? "" : "s"}.`,
      );
      resolve(entries);
    }, 1000);
  });
};

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<any[]>(
    new Array(100)
      .fill(null)
      .map(() => ({ language: languages[getRandomNumber(0, 4)] })),
  );
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    {
      field: "language",
      cellEditor: "agRichSelectCellEditor",
      width: 300,
      cellEditorParams: {
        allowTyping: true,
        filterList: true,
        values: getValueFromServer,
        filterListAsync: true,
      } as IRichCellEditorParams,
    },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      width: 200,
      editable: true,
    };
  }, []);

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

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

[Live example: Rich Select Async Filtering](https://www.ag-grid.com/archive/36.2.0/examples/provided-cell-editors-rich-select-async/rich-select-full-async-values/reactFunctionalTs/)

```js
columnDefs: [
    {
        field: 'language',
        cellEditor: 'agRichSelectCellEditor',
        cellEditorParams: {
            allowTyping: true,
            filterList: true,
            filterListAsync: true,
            values: (params) => {
                return fetch(`/api/languages?search=${encodeURIComponent(params.search)}`)
                    .then((res) => res.json())
                    .then((data) => data.items);
            }
        }
    }
]
```

## Paged Async Filtering

For very large, server-backed datasets, combine async filtering with `valuesPage` so filtered results are also loaded incrementally. `valuesPageInitialStartRow` is only used for the initial unfiltered load; once the user types, filtered paging starts from row `0`.

In this mode each request includes:

- `search` for the current filter text.
- `startRow` and `endRow` for range-based pagination.
- `cursor` (optional) for cursor-based forward pagination APIs (`undefined` on the first request, then replayed from the previous response).

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `valuesPage` | `RichCellEditorValuesPageCallback` |  |  |  |
| `valuesPageInitialStartRow` | `number \| RichCellEditorValuesPageStartRowCallback` |  |  |  |
| `valuesPageSize` | `number` |  |  |  |
| `valuesPageLoadThreshold` | `number` |  |  |  |
| `filterListAsync` | `boolean` |  |  |  |

#### Rich Select Paged Async Filtering

```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 {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  IRichCellEditorParams,
  ModuleRegistry,
  RichCellEditorValuesPageParams,
  RichCellEditorValuesPageResult,
  TextEditorModule,
  enableDevValidations,
} from "ag-grid-community";
import { RichSelectModule } from "ag-grid-enterprise";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableDevValidations();
}

const modules = [TextEditorModule, ClientSideRowModelModule, RichSelectModule];

const languages = new Array(20000)
  .fill(null)
  .map((_, index) => `Language ${index + 1}`);

function getRandomNumber(min: number, max: number) {
  // min and max included
  return Math.floor(window.agRandom() * (max - min + 1) + min);
}

const getFilteredValuePageFromServer: (
  params: RichCellEditorValuesPageParams,
) => Promise<RichCellEditorValuesPageResult<string>> = (
  params: RichCellEditorValuesPageParams,
) => {
  const search = params.search.toLowerCase();
  // Simulates an async request to a server
  return new Promise((resolve) => {
    setTimeout(() => {
      const filtered = search
        ? languages.filter((language) =>
            language.toLowerCase().includes(search),
          )
        : languages;
      const pageValues = filtered.slice(params.startRow, params.endRow);
      const nextOffset =
        params.endRow < filtered.length ? String(params.endRow) : null;
      resolve({
        values: pageValues,
        lastRow: filtered.length,
        cursor: nextOffset,
      });
    }, 300);
  });
};

const getInitialStartRowForValue: (
  value: string | null | undefined,
) => number = (value: string | null | undefined) => {
  if (!value) {
    return 0;
  }
  const match = /^Language (\d+)$/.exec(value);
  if (!match) {
    return 0;
  }
  const selectedIndex = Number(match[1]) - 1;
  return Math.max(selectedIndex - 40, 0);
};

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<any[]>(
    new Array(100)
      .fill(null)
      .map(() => ({
        language: languages[getRandomNumber(0, languages.length - 1)],
      })),
  );
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    {
      field: "language",
      width: 320,
      editable: true,
      cellEditor: "agRichSelectCellEditor",
      cellEditorParams: {
        allowTyping: true,
        filterList: true,
        filterListAsync: true,
        valuesPage: getFilteredValuePageFromServer,
        valuesPageInitialStartRow: (value: string | null | undefined) =>
          getInitialStartRowForValue(value),
        valuesPageSize: 80,
        valuesPageLoadThreshold: 8,
      } as IRichCellEditorParams,
    },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      width: 220,
      editable: true,
    };
  }, []);

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

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

[Live example: Rich Select Paged Async Filtering](https://www.ag-grid.com/archive/36.2.0/examples/provided-cell-editors-rich-select-async/rich-select-paged-async-filtering/reactFunctionalTs/)

```js
columnDefs: [
    {
        cellEditor: 'agRichSelectCellEditor',
        cellEditorParams: {
            allowTyping: true,
            filterList: true,
            filterListAsync: true,
            valuesPageInitialStartRow: (value) => getRowForSelectedValue(value),
            valuesPage: (params) => {
                    return fetch(
                        `/api/languages
                            ?search=${encodeURIComponent(params.search)}
                            &startRow=${params.startRow}
                            &endRow=${params.endRow}`
                    ).then((res) => res.json());
            },
        }
    }
]
```

## API

Properties available on the `IRichCellEditorParams&lt;TData = any, TValue = any, GValue = any&gt;` interface.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `values` | `TValue[] \| RichCellEditorValuesCallback` |  |  |  |
| `valuesPage` | `RichCellEditorValuesPageCallback` |  |  |  |
| `valuesPageInitialStartRow` | `number \| RichCellEditorValuesPageStartRowCallback` |  |  |  |
| `valuesPageSize` | `number` |  |  |  |
| `valuesPageLoadThreshold` | `number` |  |  |  |
| `cellHeight` | `number` |  |  |  |
| `cellRenderer` | `any` |  |  |  |
| `cellRendererParams` | `any` |  |  |  |
| `allowTyping` | `boolean` |  |  |  |
| `filterList` | `boolean` |  |  |  |
| `filterListAsync` | `boolean` |  |  |  |
| `searchType` | `'match' \| 'matchAny' \| 'fuzzy'` |  |  |  |
| `highlightMatch` | `boolean` |  |  |  |
| `multiSelect` | `boolean` |  |  |  |
| `suppressDeselectAll` | `boolean` |  |  |  |
| `suppressMultiSelectPillRenderer` | `boolean` |  |  |  |
| `searchDebounceDelay` | `number` |  |  |  |
| `valuePlaceholder` | `string` |  |  |  |
| `valueListGap` | `number` |  |  |  |
| `valueListMaxHeight` | `number \| string` |  |  |  |
| `valueListMaxWidth` | `number \| string` |  |  |  |
| `formatValue` | `Function` |  |  |  |
| `parseValue` | `Function` |  |  |  |
