---
title: "SSRM Transactions"
enterprise: true
framework: react
version: "36.1.0"
---

# SSRM Transactions

This section shows how rows can be added, removed and updated using the Server-Side Transaction API.

> **Note**
>
> Server-Side Transactions require [Row IDs](https://www.ag-grid.com/react-data-grid/server-side-model-configuration/#providing-row-ids) to be supplied to grid.

## Transaction API

The SSRM Transaction API allows rows to be added, removed or updated in the grid:

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `applyServerSideTransaction` | `Function` |  |  | Apply transactions to the server side row model. Module: [`ServerSideRowModelApiModule`](https://www.ag-grid.com/react-data-grid/modules/). |

> **Note**
>
> When the server-side store has a known last row index, remove transactions only delete rows that are currently in cache. If a delete is repeated or targets a row outside the loaded range, the grid ignores it and keeps the current store size. To explicitly set the new store size, provide `rowCount` on the transaction.

These operations are shown in the snippet below:

```jsx
gridApi.applyServerSideTransaction({
    add: [
        { tradeId: 101, portfolio: 'Aggressive', product: 'Aluminium', book: 'GL-62472', current: 57969 }
    ],
    update: [
        { tradeId: 102,  portfolio: 'Aggressive', product: 'Aluminium', book: 'GL-624723', current: 58927 }
    ],
    remove: [
        { tradeId: 103 }
    ]
});
```

The following example demonstrates add / update and remove operations via the Server-Side Transaction API. Note the following:

- When clicking any of the buttons, the console logs each transaction as it is applied to the grid.
- **Add Above Selected** - adds a row above the selected row using the `addIndex` property as rows are added at the end by default.
- **Update Selected** - updates the 'current' value on the selected row.
- **Removed Selected** - removes the selected row.

#### Server-Side Transaction API

```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 "./styles.css";
import {
  AutoGroupColumnDef,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  HighlightChangesModule,
  IServerSideGetRowsParams,
  ModuleRegistry,
  RowModelType,
  RowSelectionOptions,
  ServerSideTransaction,
  ServerSideTransactionResult,
  enableDevValidations,
} from "ag-grid-community";
import {
  ServerSideRowModelApiModule,
  ServerSideRowModelModule,
} from "ag-grid-enterprise";
import { data } from "./data";
import { FakeServer } from "./fakeServer";

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

const modules = [
  HighlightChangesModule,
  ServerSideRowModelModule,
  ServerSideRowModelApiModule,
];

function getServerSideDatasource(server: any) {
  return {
    getRows: (params: IServerSideGetRowsParams) => {
      const response = server.getData(params.request);
      // adding delay to simulate real server call
      setTimeout(() => {
        if (response.success) {
          // call the success callback
          params.success({
            rowData: response.rows,
            rowCount: response.lastRow,
          });
        } else {
          // inform the grid request failed
          params.fail();
        }
      }, 300);
    },
  };
}

function logResults(
  transaction: ServerSideTransaction,
  result?: ServerSideTransactionResult,
) {
  console.log(
    "[Example] - Applied transaction:",
    transaction,
    "Result:",
    result,
  );
}

function getNewValue() {
  return Math.floor(window.agRandom() * 100000) + 100;
}

let serverCurrentTradeId = data.length;

function createRow() {
  return {
    portfolio: "Aggressive",
    product: "Aluminium",
    book: "GL-62472",
    tradeId: ++serverCurrentTradeId,
    current: getNewValue(),
  };
}

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

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "tradeId" },
    { field: "portfolio" },
    { field: "book" },
    { field: "current" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 100,
      enableCellChangeFlash: true,
    };
  }, []);
  const autoGroupColumnDef = useMemo<AutoGroupColumnDef>(() => {
    return {
      minWidth: 220,
    };
  }, []);
  const getRowId = useCallback(
    (params: GetRowIdParams) => `${params.data.tradeId}`,
    [],
  );
  const rowSelection = useMemo<
    RowSelectionOptions | "single" | "multiple"
  >(() => {
    return { mode: "singleRow" };
  }, []);

  const onGridReady = useCallback((params: GridReadyEvent) => {
    // setup the fake server
    const server = new FakeServer(data);
    // create datasource with a reference to the fake server
    const datasource = getServerSideDatasource(server);
    // register the datasource with the grid
    params.api.setGridOption("serverSideDatasource", datasource);
  }, []);

  const addRow = useCallback(() => {
    const selectedRows = gridRef.current!.api.getSelectedNodes();
    if (selectedRows.length === 0) {
      console.log("[Example] No row selected.");
      return;
    }
    const rowIndex = selectedRows[0].rowIndex;
    const transaction: ServerSideTransaction = {
      addIndex: rowIndex != null ? rowIndex : undefined,
      add: [createRow()],
    };
    const result = gridRef.current!.api.applyServerSideTransaction(transaction);
    logResults(transaction, result);
  }, []);

  const updateRow = useCallback(() => {
    const selectedRows = gridRef.current!.api.getSelectedNodes();
    if (selectedRows.length === 0) {
      console.log("[Example] No row selected.");
      return;
    }
    const transaction: ServerSideTransaction = {
      update: [{ ...selectedRows[0].data, current: getNewValue() }],
    };
    const result = gridRef.current!.api.applyServerSideTransaction(transaction);
    logResults(transaction, result);
  }, []);

  const removeRow = useCallback(() => {
    const selectedRows = gridRef.current!.api.getSelectedNodes();
    if (selectedRows.length === 0) {
      console.log("[Example] No row selected.");
      return;
    }
    const transaction: ServerSideTransaction = {
      remove: [selectedRows[0].data],
    };
    const result = gridRef.current!.api.applyServerSideTransaction(transaction);
    logResults(transaction, result);
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div className="example-wrapper">
          <div style={{ marginBottom: "5px" }}>
            <button onClick={addRow}>Add Above Selected</button>
            <button onClick={updateRow}>Update Selected</button>
            <button onClick={removeRow}>Remove Selected</button>
          </div>

          <div style={gridStyle}>
            <AgGridReact
              ref={gridRef}
              columnDefs={columnDefs}
              defaultColDef={defaultColDef}
              autoGroupColumnDef={autoGroupColumnDef}
              getRowId={getRowId}
              rowSelection={rowSelection}
              rowModelType={"serverSide"}
              onGridReady={onGridReady}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Server-Side Transaction API](https://www.ag-grid.com/examples/server-side-model-updating-transactions/transactions-simple/reactFunctionalTs)

## Row Grouping

To use transactions while using row grouping, transactions need to be applied to the specific row group. This is done by providing a `route` when applying the transaction. It is also necessary to inform the grid when group rows are updated, added or removed.

The snippet below demonstrates creating a group row transaction for rows which are the first of their group, as the leaf rows will be requested via `getRows` when the group is expanded.

```jsx
// create the group row at the root level (only if it's the first row for this group)
gridApi.applyServerSideTransaction({
	route: [],
	add: [{ portfolio: 'Aggressive' }]
});

// otherwise, create the leaf node inside of the 'Aggressive' group
gridApi.applyServerSideTransaction({
	route: ['Aggressive'],
	add: [row]
});
```

In the example below, note the following:

- When clicking any of the buttons, the console logs each transaction as it is applied to the grid.
- To add a new row, if the group didn't previously exist, then the route is omitted and the group row is added. If it did previously exist, then the group route is provided and the leaf node is added.
- To delete a row, if the group row would be deleted then a transaction needs to be applied to remove this group row instead of the leaf row.
- To move a row between groups, the row needs to be deleted from the old group with one transaction, and added to the new group with another.

#### Transactions With Groups

```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 "./styles.css";
import {
  AutoGroupColumnDef,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  HighlightChangesModule,
  IServerSideGetRowsParams,
  IsServerSideGroupOpenByDefault,
  IsServerSideGroupOpenByDefaultParams,
  ModuleRegistry,
  RowModelType,
  ServerSideTransaction,
  ServerSideTransactionResult,
  enableDevValidations,
} from "ag-grid-community";
import {
  RowGroupingModule,
  ServerSideRowModelApiModule,
  ServerSideRowModelModule,
} from "ag-grid-enterprise";
import {
  changePortfolioOnServer,
  createRowOnServer,
  data,
  deletePortfolioOnServer,
} from "./data";
import { FakeServer } from "./fakeServer";

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

const modules = [
  HighlightChangesModule,
  RowGroupingModule,
  ServerSideRowModelModule,
  ServerSideRowModelApiModule,
];

function getServerSideDatasource(server: any) {
  return {
    getRows: (params: IServerSideGetRowsParams) => {
      const response = server.getData(params.request);
      // adding delay to simulate real server call
      setTimeout(() => {
        if (response.success) {
          // call the success callback
          params.success({
            rowData: response.rows,
            rowCount: response.lastRow,
          });
        } else {
          // inform the grid request failed
          params.fail();
        }
      }, 300);
    },
  };
}

function logResults(
  transaction: ServerSideTransaction,
  result?: ServerSideTransactionResult,
) {
  console.log(
    "[Example] - Applied transaction:",
    transaction,
    "Result:",
    result,
  );
}

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

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "tradeId" },
    { field: "portfolio", hide: true, rowGroup: true },
    { field: "book" },
    { field: "previous" },
    { field: "current" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 100,
      enableCellChangeFlash: true,
    };
  }, []);
  const autoGroupColumnDef = useMemo<AutoGroupColumnDef>(() => {
    return {
      minWidth: 220,
    };
  }, []);
  const isServerSideGroupOpenByDefault = useCallback(
    (params: IsServerSideGroupOpenByDefaultParams) => {
      return (
        params.rowNode.key === "Aggressive" || params.rowNode.key === "Hybrid"
      );
    },
    [],
  );
  const getRowId = useCallback((params: GetRowIdParams) => {
    if (params.level === 0) {
      return params.data.portfolio;
    }
    return String(params.data.tradeId);
  }, []);

  const onGridReady = useCallback((params: GridReadyEvent) => {
    // setup the fake server
    const server = new FakeServer(data);
    // create datasource with a reference to the fake server
    const datasource = getServerSideDatasource(server);
    // register the datasource with the grid
    params.api.setGridOption("serverSideDatasource", datasource);
  }, []);

  const deleteAllHybrid = useCallback(() => {
    // NOTE: real applications would be better served listening to a stream of changes from the server instead
    const serverResponse: any = deletePortfolioOnServer("Hybrid");
    if (!serverResponse.success) {
      console.warn("Nothing has changed on the server");
      return;
    }
    if (serverResponse) {
      // apply tranaction to keep grid in sync
      const transaction = {
        remove: [{ portfolio: "Hybrid" }],
      };
      const result =
        gridRef.current!.api.applyServerSideTransaction(transaction);
      logResults(transaction, result);
    }
  }, [deletePortfolioOnServer]);

  const createOneAggressive = useCallback(() => {
    // NOTE: real applications would be better served listening to a stream of changes from the server instead
    const serverResponse: any = createRowOnServer(
      "Aggressive",
      "Aluminium",
      "GL-1",
    );
    if (!serverResponse.success) {
      console.warn("Nothing has changed on the server");
      return;
    }
    if (serverResponse.newGroupCreated) {
      // if a new group had to be created, reflect in the grid
      const transaction = {
        route: [],
        add: [{ portfolio: "Aggressive" }],
      };
      const result =
        gridRef.current!.api.applyServerSideTransaction(transaction);
      logResults(transaction, result);
    } else {
      // if the group already existed, add rows to it
      const transaction = {
        route: ["Aggressive"],
        add: [serverResponse.newRecord],
      };
      const result =
        gridRef.current!.api.applyServerSideTransaction(transaction);
      logResults(transaction, result);
    }
  }, [createRowOnServer]);

  const updateAggressiveToHybrid = useCallback(() => {
    // NOTE: real applications would be better served listening to a stream of changes from the server instead
    const serverResponse: any = changePortfolioOnServer("Aggressive", "Hybrid");
    if (!serverResponse.success) {
      console.warn("Nothing has changed on the server");
      return;
    }
    const transaction = {
      remove: [{ portfolio: "Aggressive" }],
    };
    // aggressive group no longer exists, so delete the group
    const result = gridRef.current!.api.applyServerSideTransaction(transaction);
    logResults(transaction, result);
    if (serverResponse.newGroupCreated) {
      // hybrid group didn't exist, so just create the new group
      const t = {
        route: [],
        add: [{ portfolio: "Hybrid" }],
      };
      const r = gridRef.current!.api.applyServerSideTransaction(t);
      logResults(t, r);
    } else {
      // hybrid group already existed, add rows to it
      const t = {
        route: ["Hybrid"],
        add: serverResponse.updatedRecords,
      };
      const r = gridRef.current!.api.applyServerSideTransaction(t);
      logResults(t, r);
    }
  }, [changePortfolioOnServer]);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div className="example-wrapper">
          <div style={{ marginBottom: "5px" }}>
            <button onClick={createOneAggressive}>Add new 'Aggressive'</button>
            <button onClick={updateAggressiveToHybrid}>
              Move all 'Aggressive' to 'Hybrid'
            </button>
            <button onClick={deleteAllHybrid}>Remove all 'Hybrid'</button>
          </div>

          <div style={gridStyle}>
            <AgGridReact
              ref={gridRef}
              columnDefs={columnDefs}
              defaultColDef={defaultColDef}
              autoGroupColumnDef={autoGroupColumnDef}
              isServerSideGroupOpenByDefault={isServerSideGroupOpenByDefault}
              getRowId={getRowId}
              rowModelType={"serverSide"}
              onGridReady={onGridReady}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Transactions With Groups](https://www.ag-grid.com/examples/server-side-model-updating-transactions/transactions-grouping/reactFunctionalTs)

## Asynchronous Updates

When processing many updates rapidly, the grid will perform more smoothly if the changes are batched (as this can prevent excessive rendering). The grid can batch these changes for you without negatively impacting the user experience, and in most cases improving it.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `applyServerSideTransactionAsync` | `Function` |  |  | Batch apply transactions to the server side row model. Module: [`ServerSideRowModelApiModule`](https://www.ag-grid.com/react-data-grid/modules/). |

When using asynchronous transactions, the grid delays any transactions received within a time window (specified using `asyncTransactionWaitMillis`) and executes them together when the window has passed.

The snippet below demonstrates three asynchronous transactions applied sequentially, however because these transactions are asynchronously batched, the grid would only update the DOM once.

```jsx
// due to asynchronous batching, the following transactions are applied together preventing unnecessary DOM updates
gridApi.applyServerSideTransactionAsync({
    add: [{ tradeId: 101, portfolio: 'Aggressive', product: 'Aluminium', book: 'GL-62472', current: 57969 }],
});
gridApi.applyServerSideTransactionAsync({
    update: [{ tradeId: 102,  portfolio: 'Aggressive', product: 'Aluminium', book: 'GL-624723', current: 58927 }],
});
gridApi.applyServerSideTransactionAsync({
    remove: [{ tradeId: 103 }],
});
```

In the example below, note the following:

- After starting the updates, 1 row is created, 10 rows are updated, and 1 row is deleted every 10 milliseconds.
- The transactions are batched, and only executed once every second.

#### Asynchronous Example

```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 "./styles.css";
import {
  AutoGroupColumnDef,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  HighlightChangesModule,
  IServerSideGetRowsParams,
  ModuleRegistry,
  RowModelType,
  ServerSideTransaction,
  enableDevValidations,
} from "ag-grid-community";
import {
  ServerSideRowModelApiModule,
  ServerSideRowModelModule,
} from "ag-grid-enterprise";
import { data, dataObservers, randomUpdates } from "./data";
import { FakeServer } from "./fakeServer";

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

const modules = [
  HighlightChangesModule,
  ServerSideRowModelModule,
  ServerSideRowModelApiModule,
];

function getServerSideDatasource(server: any) {
  return {
    getRows: (params: IServerSideGetRowsParams) => {
      const response = server.getData(params.request);
      // adding delay to simulate real server call
      setTimeout(() => {
        if (response.success) {
          // call the success callback
          params.success({
            rowData: response.rows,
            rowCount: response.lastRow,
          });
        } else {
          // inform the grid request failed
          params.fail();
        }
      }, 300);
    },
  };
}

let interval: any;

function disable(id: string, disabled: boolean) {
  document.querySelector<HTMLInputElement>(id)!.disabled = disabled;
}

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

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "tradeId" },
    { field: "portfolio" },
    { field: "book" },
    { field: "previous" },
    { field: "current" },
    {
      field: "lastUpdated",
      wrapHeaderText: true,
      autoHeaderHeight: true,
      valueFormatter: (params) => {
        const ts = params.data!.lastUpdated;
        if (ts) {
          const hh_mm_ss = ts.toLocaleString().split(" ")[1];
          const SSS = ts.getMilliseconds();
          return `${hh_mm_ss}:${SSS}`;
        }
        return "";
      },
    },
    { field: "updateCount" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 100,
      enableCellChangeFlash: true,
    };
  }, []);
  const autoGroupColumnDef = useMemo<AutoGroupColumnDef>(() => {
    return {
      minWidth: 220,
    };
  }, []);
  const getRowId = useCallback((params: GetRowIdParams) => {
    let rowId = "";
    if (params.parentKeys && params.parentKeys.length) {
      rowId += params.parentKeys.join("-") + "-";
    }
    if (params.data.tradeId != null) {
      rowId += params.data.tradeId;
    }
    return rowId;
  }, []);

  const onGridReady = useCallback((params: GridReadyEvent) => {
    disable("#stopUpdates", true);
    // setup the fake server
    const server = FakeServer(data);
    // create datasource with a reference to the fake server
    const datasource = getServerSideDatasource(server);
    // register the datasource with the grid
    params.api.setGridOption("serverSideDatasource", datasource);
    // register interest in data changes
    dataObservers.push((t: ServerSideTransaction) => {
      params.api.applyServerSideTransactionAsync(t);
    });
  }, []);

  const startUpdates = useCallback(() => {
    interval = setInterval(
      () => randomUpdates({ numUpdate: 10, numAdd: 1, numRemove: 1 }),
      10,
    );
    disable("#stopUpdates", false);
    disable("#startUpdates", true);
  }, [randomUpdates]);

  const stopUpdates = useCallback(() => {
    if (interval !== undefined) {
      clearInterval(interval);
    }
    disable("#stopUpdates", true);
    disable("#startUpdates", false);
  }, [interval]);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div className="example-wrapper">
          <div style={{ marginBottom: "5px" }}>
            <button id="startUpdates" onClick={startUpdates}>
              Start Updates
            </button>
            <button id="stopUpdates" onClick={stopUpdates}>
              Stop Updates
            </button>
          </div>

          <div style={gridStyle}>
            <AgGridReact
              columnDefs={columnDefs}
              defaultColDef={defaultColDef}
              autoGroupColumnDef={autoGroupColumnDef}
              getRowId={getRowId}
              asyncTransactionWaitMillis={1000}
              rowModelType={"serverSide"}
              onGridReady={onGridReady}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Asynchronous Example](https://www.ag-grid.com/examples/server-side-model-updating-transactions/transactions-async/reactFunctionalTs)

## Showcase Example

The following demonstrates a more complex example of transactions, it shows subscribing to a source of updates to provide the changes, while using dynamic row grouping, aggregation, and child counts. All of which react to the changes caused by the transactions.

In the example below, note the following:

- After starting the updates, 2 rows are created, 5 rows are updated, and 2 rows are deleted once every second.
- Groups are created or destroyed when necessary by using transactions.
- The group panel has been enabled, allowing a dynamic configuration of groups.
- The group child counts and aggregations update in sync with changes to the leaf rows.

#### Showcase Example

```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 "./styles.css";
import {
  AutoGroupColumnDef,
  ColDef,
  ColGroupDef,
  ColumnApiModule,
  ColumnRowGroupChangedEvent,
  GetChildCount,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  HighlightChangesModule,
  IServerSideGetRowsParams,
  IsServerSideGroupOpenByDefault,
  IsServerSideGroupOpenByDefaultParams,
  ModuleRegistry,
  RowModelType,
  ServerSideTransaction,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  RowGroupingModule,
  RowGroupingPanelModule,
  ServerSideRowModelApiModule,
  ServerSideRowModelModule,
} from "ag-grid-enterprise";
import { getFakeServer, registerObserver } from "./fakeServer";

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

const modules = [
  TextFilterModule,
  HighlightChangesModule,
  ColumnApiModule,
  RowGroupingModule,
  ServerSideRowModelModule,
  ServerSideRowModelApiModule,
  RowGroupingPanelModule,
];

function disable(id: string, disabled: boolean) {
  document.querySelector<HTMLInputElement>(id)!.disabled = disabled;
}

function getServerSideDatasource(server: any) {
  return {
    getRows: (params: IServerSideGetRowsParams) => {
      console.log("[Datasource] - rows requested by grid: ", params.request);
      const response = server.getData(params.request);
      // adding delay to simulate real server call
      setTimeout(() => {
        if (response.success) {
          // call the success callback
          params.success({
            rowData: response.rows,
            rowCount: response.lastRow,
          });
        } else {
          // inform the grid request failed
          params.fail();
        }
      }, 300);
    },
  };
}

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

  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "tradeId" },
    {
      field: "product",
      rowGroup: true,
      enableRowGroup: true,
      hide: true,
    },
    {
      field: "portfolio",
      rowGroup: true,
      enableRowGroup: true,
      hide: true,
    },
    {
      field: "book",
      rowGroup: true,
      enableRowGroup: true,
      hide: true,
    },
    { field: "previous", aggFunc: "sum" },
    { field: "current", aggFunc: "sum" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      flex: 1,
      minWidth: 100,
      enableCellChangeFlash: true,
    };
  }, []);
  const autoGroupColumnDef = useMemo<AutoGroupColumnDef>(() => {
    return {
      minWidth: 220,
    };
  }, []);

  const onGridReady = useCallback((params: GridReadyEvent) => {
    disable("#stopUpdates", true);
    // create datasource with a reference to the fake server
    const datasource = getServerSideDatasource(getFakeServer());
    // register the datasource with the grid
    params.api.setGridOption("serverSideDatasource", datasource);
    // register interest in data changes
    registerObserver({
      transactionFunc: (t: ServerSideTransaction) =>
        params.api.applyServerSideTransactionAsync(t),
      groupedFields: ["product", "portfolio", "book"],
    });
  }, []);

  const onColumnRowGroupChanged = useCallback(
    (event: ColumnRowGroupChangedEvent) => {
      const colState = event.api.getColumnState();
      const groupedColumns = colState.filter((state) => state.rowGroup);
      groupedColumns.sort((a, b) => a.rowGroupIndex! - b.rowGroupIndex!);
      const groupedFields = groupedColumns.map((col) => col.colId);
      registerObserver({
        transactionFunc: (t: ServerSideTransaction) =>
          gridRef.current!.api.applyServerSideTransactionAsync(t),
        groupedFields: groupedFields.length === 0 ? undefined : groupedFields,
      });
    },
    [registerObserver],
  );

  const startUpdates = useCallback(() => {
    getFakeServer().randomUpdates();
    disable("#startUpdates", true);
    disable("#stopUpdates", false);
  }, [getFakeServer]);

  const stopUpdates = useCallback(() => {
    getFakeServer().stopUpdates();
    disable("#stopUpdates", true);
    disable("#startUpdates", false);
  }, [getFakeServer]);

  const getChildCount = useCallback((data: any) => {
    return data ? data.childCount : undefined;
  }, []);

  const getRowId = useCallback((params: GetRowIdParams) => {
    let rowId = "";
    if (params.parentKeys && params.parentKeys.length) {
      rowId += params.parentKeys.join("-") + "-";
    }
    const groupCols = params.api.getRowGroupColumns();
    if (groupCols.length > params.level) {
      const thisGroupCol = groupCols[params.level];
      rowId += params.data[thisGroupCol.getColDef().field!] + "-";
    }
    if (params.data.tradeId != null) {
      rowId += params.data.tradeId;
    }
    return rowId;
  }, []);

  const isServerSideGroupOpenByDefault = useCallback(
    (params: IsServerSideGroupOpenByDefaultParams) => {
      const route = params.rowNode.getRoute();
      if (!route) {
        return false;
      }
      const routeAsString = route.join(",");
      return (
        ["Wool", "Wool,Aggressive", "Wool,Aggressive,GL-62502"].indexOf(
          routeAsString,
        ) >= 0
      );
    },
    [],
  );

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div className="grid-container">
          <div>
            <button id="startUpdates" onClick={startUpdates}>
              Start Updates
            </button>
            <button id="stopUpdates" onClick={stopUpdates}>
              Stop Updates
            </button>
          </div>

          <div style={gridStyle}>
            <AgGridReact
              ref={gridRef}
              columnDefs={columnDefs}
              defaultColDef={defaultColDef}
              autoGroupColumnDef={autoGroupColumnDef}
              rowGroupPanelShow={"always"}
              purgeClosedRowNodes={true}
              rowModelType={"serverSide"}
              getChildCount={getChildCount}
              getRowId={getRowId}
              isServerSideGroupOpenByDefault={isServerSideGroupOpenByDefault}
              onGridReady={onGridReady}
              onColumnRowGroupChanged={onColumnRowGroupChanged}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Showcase Example](https://www.ag-grid.com/examples/server-side-model-updating-transactions/transactions-showcase/reactFunctionalTs)

## Tree Data

Transactions are also supported when using tree data. See this documented on the [SSRM Tree Data](https://www.ag-grid.com/react-data-grid/server-side-model-tree-data/#transactions-with-tree-data) page.
