---
title: "Updating Row Data"
framework: react
version: "36.1.0"
---

# Updating Row Data

Update the Row Data inside the grid by updating the `rowData` grid property.

The example below shows the grid with two sets of data. Clicking the buttons toggles between the data sets or clears the row data. Some rows are common between the dataset, however if any row is selected (by clicking the row), the selection is lost between row updates as row ids are not provided.

#### Simple Row Data

```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 {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  ModuleRegistry,
  RowSelectionModule,
  RowSelectionOptions,
  enableDevValidations,
} from "ag-grid-community";

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

const modules = [
  RowSelectionModule,
  ClientSideRowModelModule,
  ClientSideRowModelApiModule,
];

interface ICar {
  make: string;
  model: string;
  price: number;
}

// specify the data
const rowDataA: ICar[] = [
  { make: "Toyota", model: "Celica", price: 35000 },
  { make: "Porsche", model: "Boxster", price: 72000 },
  { make: "Aston Martin", model: "DBX", price: 190000 },
];

const rowDataB: ICar[] = [
  { make: "Toyota", model: "Celica", price: 35000 },
  { make: "Ford", model: "Mondeo", price: 32000 },
  { make: "Porsche", model: "Boxster", price: 72000 },
  { make: "BMW", model: "M50", price: 60000 },
  { make: "Aston Martin", model: "DBX", price: 190000 },
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<ICar[]>(rowDataA);
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "make" },
    { field: "model" },
    { field: "price" },
  ]);
  const rowSelection = useMemo<
    RowSelectionOptions | "single" | "multiple"
  >(() => {
    return { mode: "singleRow", checkboxes: false, enableClickSelection: true };
  }, []);

  const onRowDataA = useCallback(() => {
    setRowData(rowDataA);
  }, [rowDataA]);

  const onRowDataB = useCallback(() => {
    setRowData(rowDataB);
  }, [rowDataB]);

  const onClearRowData = useCallback(() => {
    // Clear rowData by setting it to an empty array
    setRowData([]);
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div
          style={{
            height: "100%",
            width: "100%",
            display: "flex",
            flexDirection: "column",
          }}
        >
          <div style={{ marginBottom: "5px", minHeight: "30px" }}>
            <button onClick={onRowDataA}>Row Data A</button>
            <button onClick={onRowDataB}>Row Data B</button>
            <button onClick={onClearRowData}>Clear Row Data</button>
          </div>
          <div style={{ flex: "1 1 0px" }}>
            <div style={gridStyle}>
              <AgGridReact<ICar>
                rowData={rowData}
                columnDefs={columnDefs}
                rowSelection={rowSelection}
              />
            </div>
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Simple Row Data](https://www.ag-grid.com/examples/data-update-row-data/simple-row-data/reactFunctionalTs)

The example below is identical to the above except [Row IDs](https://www.ag-grid.com/react-data-grid/row-ids/) are provided via the `getRowId()` callback. This results in [Row Selection](https://www.ag-grid.com/react-data-grid/row-selection/) being maintained across Row Data changes (assuming the Row exists in both sets) and the HTML is not redrawn from scratch, resulting in [Row Animations](https://www.ag-grid.com/react-data-grid/row-animation/).

#### Simple Row ID

```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 {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  ModuleRegistry,
  RowSelectionModule,
  RowSelectionOptions,
  enableDevValidations,
} from "ag-grid-community";

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

const modules = [
  RowSelectionModule,
  ClientSideRowModelModule,
  ClientSideRowModelApiModule,
];

interface ICar {
  id: string;
  make: string;
  model: string;
  price: number;
}

// specify the data
const rowDataA: ICar[] = [
  { id: "1", make: "Toyota", model: "Celica", price: 35000 },
  { id: "4", make: "BMW", model: "M50", price: 60000 },
  { id: "5", make: "Aston Martin", model: "DBX", price: 190000 },
];

const rowDataB: ICar[] = [
  { id: "1", make: "Toyota", model: "Celica", price: 35000 },
  { id: "2", make: "Ford", model: "Mondeo", price: 32000 },
  { id: "3", make: "Porsche", model: "Boxster", price: 72000 },
  { id: "4", make: "BMW", model: "M50", price: 60000 },
  { id: "5", make: "Aston Martin", model: "DBX", price: 190000 },
];

const GridExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState<ICar[]>(rowDataA);
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { field: "make" },
    { field: "model" },
    { field: "price" },
  ]);
  const rowSelection = useMemo<
    RowSelectionOptions | "single" | "multiple"
  >(() => {
    return { mode: "singleRow", checkboxes: false, enableClickSelection: true };
  }, []);
  const getRowId = useCallback(
    (params: GetRowIdParams<ICar>) => params.data.id,
    [],
  );

  const onRowDataA = useCallback(() => {
    setRowData(rowDataA);
  }, [rowDataA]);

  const onRowDataB = useCallback(() => {
    setRowData(rowDataB);
  }, [rowDataB]);

  const onClearRowData = useCallback(() => {
    setRowData([]);
  }, []);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div
          style={{
            height: "100%",
            width: "100%",
            display: "flex",
            flexDirection: "column",
          }}
        >
          <div style={{ marginBottom: "5px", minHeight: "30px" }}>
            <button onClick={onRowDataA}>Row Data A</button>
            <button onClick={onRowDataB}>Row Data B</button>
            <button onClick={onClearRowData}>Clear Row Data</button>
          </div>
          <div style={{ flex: "1 1 0px" }}>
            <div style={gridStyle}>
              <AgGridReact<ICar>
                rowData={rowData}
                columnDefs={columnDefs}
                rowSelection={rowSelection}
                getRowId={getRowId}
              />
            </div>
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Simple Row ID](https://www.ag-grid.com/examples/data-update-row-data/simple-row-id/reactFunctionalTs)

Providing [Row IDs](https://www.ag-grid.com/react-data-grid/row-ids/) allows the grid to work optimally in a few areas which are outlined as follows:

| Function | Row IDs Missing | Row IDs Provided |
| --- | --- | --- |
| [Row Selection](https://www.ag-grid.com/react-data-grid/row-selection/) | Row Selection lost | Row Selection maintained |
| [Row Grouping](https://www.ag-grid.com/react-data-grid/grouping/) | Row Groups re-created, all open groups closed | Groups kept / updated, open groups stay open |
| [Row Refresh](https://www.ag-grid.com/react-data-grid/view-refresh/) | All rows destroyed from the DOM and recreated, flicker may occur | Only changed rows are updated in the DOM |
| [Row Animation](https://www.ag-grid.com/react-data-grid/row-animation/) | No row animation | Moved rows animate to new position |
| [Flashing Cells](https://www.ag-grid.com/react-data-grid/change-cell-renderers/#flashing-cells) | No flashing available, all cells are created from scratch | Changed values can be flashed to show change |

## Controlling Row Position

The example below demonstrates controlling the grid rows, including their order, by updating the Row Data.

The example keeps a list of records to mimic data in a "store". Each time the user does an update, the data in the store is copied, so that when Row Data is given to the grid, the grid is presented with different Row Data. This is equivalent to refreshing data from a server, or using an Immutable Data store on the client.

Note the following:

- **Reverse**: Reverses the order of the items. The rows are moved rather than recreated. No flicker.
- **Append Items**: Adds five items to the end. The rows are moved rather than recreated. No flicker.
- **Prepend Items**: Adds five items to the start. No flicker.
- Note that if a grid sort is applied, the grid sorting order gets preference to the order of the data in the provided list.
- **Remove Selected**: Removes the selected items. Try selecting multiple rows (using the checkboxes, `⇧ Shift` + click for range) and remove multiple rows at the same time. Notice how the remaining rows animate to new positions.
- **Update Prices**: Updates all the prices. Try ordering by price and notice the order change as the prices change. Also try highlighting a range on prices and see the aggregations appear in the status bar. As you update the prices, the aggregation values recalculate.
- **Turn Grouping On / Off**: To turn grouping by symbol on and off.
- **Group Selected A / B / C**: With grouping on, hit the buttons Move to Group A, B and C to move selected items to that group. Notice how the rows animate to the new position.

(Note: the example uses the Enterprise-only features [Row Grouping](https://www.ag-grid.com/react-data-grid/grouping/), [Cell Selection](https://www.ag-grid.com/react-data-grid/cell-selection/) and [Status Bar](https://www.ag-grid.com/react-data-grid/status-bar/).)

#### Simple Immutable Store

```tsx
'use client';
import React, {
  StrictMode,
  useCallback,
  useMemo,
  useRef,
  useState,
} from "react";
import { createRoot } from "react-dom/client";

import type {
  ColDef,
  GetRowIdParams,
  GridApi,
  GridReadyEvent,
  RowSelectionOptions,
} from "ag-grid-community";
import {
  ClientSideRowModelModule,
  ColumnApiModule,
  RowSelectionModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  CellSelectionModule,
  RowGroupingModule,
  StatusBarModule,
} from "ag-grid-enterprise";
import { AgGridProvider, AgGridReact } from "ag-grid-react";

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

const modules = [
  ColumnApiModule,
  TextFilterModule,
  RowSelectionModule,
  ClientSideRowModelModule,
  RowGroupingModule,
  StatusBarModule,
  CellSelectionModule,
];

// creates a unique symbol, eg 'ADG' or 'ZJD'
function createUniqueRandomSymbol(data: any[]) {
  let symbol: string = "";
  const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  let isUnique = false;
  while (!isUnique) {
    symbol = "";
    // create symbol
    for (let i = 0; i < 3; i++) {
      symbol += possible.charAt(Math.floor(window.agRandom() * possible.length));
    }
    // check uniqueness
    isUnique = true;
    data.forEach(function (oldItem) {
      if (oldItem.symbol === symbol) {
        isUnique = false;
      }
    });
  }
  return symbol;
}

function getInitialData() {
  const data: any[] = [];
  for (let i = 0; i < 5; i++) {
    data.push(createItem(data));
  }
  return data;
}

function createItem(data: any[]) {
  const item = {
    group: ["A", "B", "C"][Math.floor(window.agRandom() * 3)],
    symbol: createUniqueRandomSymbol(data),
    price: Math.floor(window.agRandom() * 100),
  };
  return item;
}

function setGroupingEnabled(enabled: boolean, api: GridApi) {
  if (enabled) {
    api.applyColumnState({
      state: [
        { colId: "group", rowGroup: true, hide: true },
        { colId: "symbol", hide: true },
      ],
    });
  } else {
    api.applyColumnState({
      state: [
        { colId: "group", rowGroup: false, hide: false },
        { colId: "symbol", hide: false },
      ],
    });
  }
  setItemVisible("groupingOn", !enabled);
  setItemVisible("groupingOff", enabled);
}

function setItemVisible(id: string, visible: boolean) {
  const element = document.querySelector("#" + id) as any;
  element.style.display = visible ? "inline" : "none";
}

const rowSelection: RowSelectionOptions = {
  mode: "multiRow",
  groupSelects: "descendants",
  headerCheckbox: false,
};

const GridExample = () => {
  const gridRef = useRef<AgGridReact>(null);
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState(getInitialData());
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { headerName: "Symbol", field: "symbol" },
    { headerName: "Price", field: "price" },
    { headerName: "Group", field: "group" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      width: 250,
    };
  }, []);
  const autoGroupColumnDef = useMemo<ColDef>(() => {
    return {
      headerName: "Symbol",
      cellRenderer: "agGroupCellRenderer",
      field: "symbol",
    };
  }, []);
  const statusBar = useMemo(() => {
    return {
      statusPanels: [{ statusPanel: "agAggregationComponent", align: "right" }],
    };
  }, []);
  const getRowId = useCallback(function (params: GetRowIdParams) {
    return params.data.symbol;
  }, []);

  const onGridReady = useCallback((params: GridReadyEvent) => {
    setGroupingEnabled(false, params.api);
  }, []);

  const addFiveItems = useCallback(
    (append: boolean) => {
      const newStore = rowData.slice();
      for (let i = 0; i < 5; i++) {
        const newItem = createItem(newStore);
        if (append) {
          newStore.push(newItem);
        } else {
          newStore.splice(0, 0, newItem);
        }
      }
      setRowData(newStore);
    },
    [rowData],
  );

  const removeSelected = useCallback(() => {
    const selectedRowNodes = gridRef.current!.api.getSelectedNodes();
    const selectedIds = selectedRowNodes.map(function (rowNode) {
      return rowNode.id;
    });
    const filteredData = rowData.filter(function (dataItem) {
      return selectedIds.indexOf(dataItem.symbol) < 0;
    });
    setRowData(filteredData);
  }, [rowData]);

  const setSelectedToGroup = useCallback(
    (newGroup: string) => {
      const selectedRowNodes = gridRef.current!.api.getSelectedNodes();
      const selectedIds = selectedRowNodes.map(function (rowNode) {
        return rowNode.id;
      });
      const newData = rowData.map(function (dataItem) {
        const itemSelected = selectedIds.indexOf(dataItem.symbol) >= 0;
        if (itemSelected) {
          return {
            // symbol and price stay the same
            symbol: dataItem.symbol,
            price: dataItem.price,
            // group gets the group
            group: newGroup,
          };
        } else {
          return dataItem;
        }
      });
      setRowData(newData);
    },
    [rowData],
  );

  const updatePrices = useCallback(() => {
    const newStore: any[] = [];
    rowData.forEach(function (item) {
      newStore.push({
        // use same symbol as last time, this is the unique id
        symbol: item.symbol,
        // group also stays the same
        group: item.group,
        // add random price
        price: Math.floor(window.agRandom() * 100),
      });
    });
    setRowData(newStore);
  }, [rowData]);

  const onGroupingEnabled = useCallback((enabled: boolean) => {
    setGroupingEnabled(enabled, gridRef.current!.api);
  }, []);

  const reverseItems = useCallback(() => {
    const reversedData = rowData.slice().reverse();
    setRowData(reversedData);
  }, [rowData]);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div
          style={{
            height: "100%",
            width: "100%",
            display: "flex",
            flexDirection: "column",
          }}
        >
          <div>
            <div style={{ marginBottom: "5px", minHeight: "30px" }}>
              <button onClick={reverseItems}>Reverse</button>
              <button onClick={() => addFiveItems(true)}>Append</button>
              <button onClick={() => addFiveItems(false)}>Prepend</button>
              <button onClick={removeSelected}>Remove Selected</button>
              <button onClick={updatePrices}>Update Prices</button>
            </div>
            <div style={{ marginBottom: "5px", minHeight: "30px" }}>
              <button id="groupingOn" onClick={() => onGroupingEnabled(true)}>
                Grouping On
              </button>
              <button id="groupingOff" onClick={() => onGroupingEnabled(false)}>
                Grouping Off
              </button>
              <button onClick={() => setSelectedToGroup("A")}>
                Move to Group A
              </button>
              <button onClick={() => setSelectedToGroup("B")}>
                Move to Group B
              </button>
              <button onClick={() => setSelectedToGroup("C")}>
                Move to Group C
              </button>
            </div>
          </div>
          <div style={{ flex: "1 1 0px" }}>
            <div style={gridStyle}>
              <AgGridReact
                ref={gridRef}
                rowData={rowData}
                columnDefs={columnDefs}
                defaultColDef={defaultColDef}
                rowSelection={rowSelection}
                cellSelection={true}
                autoGroupColumnDef={autoGroupColumnDef}
                statusBar={statusBar}
                groupDefaultExpanded={1}
                getRowId={getRowId}
                onGridReady={onGridReady}
              />
            </div>
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Simple Immutable Store](https://www.ag-grid.com/examples/data-update-row-data/simple-immutable-store/reactFunctionalTs)

## How It Works

When providing Row IDs, the grid assumes it is fed with data from an immutable store where the following is true about the data:

- Changes to a single row data item results in a new row data item object instance.
- Any changes within the list or row data results in a new list.

The grid works out what changes need to be applied to the grid using the following rules:

- If the ID for the new item doesn't have a corresponding item already in the grid then it's added as a new row to the grid.
- If the ID for the new item does have a corresponding item in the grid then compare the object references. If the object references are different, the row is updated with the new data, otherwise it's assumed the data is the same as the already present data.
- If there are items in the grid for which there are no corresponding items in the new data, then those rows are removed.
- Lastly the rows in the grid are sorted to match the order in the newly provided list.

## Example: Immutable Store - Large Dataset

Below is a dataset with over 11,000 rows with Row Grouping and Aggregation over multiple columns. As far as Client-Side Row Data goes, this is a fairly complex grid. From the example, note the following:

- Row IDs are provided using the callback `getRowId()`.
- Selecting the Update button updates a range of the data.
- Note that all grid state (row and range selections, filters, sorting etc.) remain after updates are applied.

#### Complex Immutable Store

```tsx
'use client';
import React, {
  StrictMode,
  useCallback,
  useEffect,
  useMemo,
  useState,
} from "react";
import { createRoot } from "react-dom/client";

import type {
  ColDef,
  GetRowIdParams,
  RowSelectionOptions,
  ValueFormatterParams,
  ValueGetterParams,
} from "ag-grid-community";
import {
  CellStyleModule,
  ClientSideRowModelModule,
  HighlightChangesModule,
  NumberFilterModule,
  RowSelectionModule,
  TextEditorModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule } from "ag-grid-enterprise";
import { AgGridProvider, AgGridReact } from "ag-grid-react";

import "./styles.css";

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

const modules = [
  HighlightChangesModule,
  TextEditorModule,
  NumberFilterModule,
  TextFilterModule,
  RowSelectionModule,
  CellStyleModule,
  ClientSideRowModelModule,
  RowGroupingModule,
];

const MIN_BOOK_COUNT = 10;
const MAX_BOOK_COUNT = 20;
const MIN_TRADE_COUNT = 1;
const MAX_TRADE_COUNT = 10;
const products = [
  "Palm Oil",
  "Rubber",
  "Wool",
  "Amber",
  "Copper",
  "Lead",
  "Zinc",
  "Tin",
  "Aluminium",
  "Aluminium Alloy",
  "Nickel",
  "Cobalt",
  "Molybdenum",
  "Recycled Steel",
  "Corn",
  "Oats",
  "Rough Rice",
  "Soybeans",
  "Rapeseed",
  "Soybean Meal",
  "Soybean Oil",
  "Wheat",
  "Milk",
  "Coca",
  "Coffee C",
  "Cotton No.2",
  "Sugar No.11",
  "Sugar No.14",
];

const portfolios = [
  "Aggressive",
  "Defensive",
  "Income",
  "Speculative",
  "Hybrid",
];

// as we create books, we remember what products they belong to, so we can
// add to these books later when use clicks one of the buttons
const productToPortfolioToBooks: any = {};

// start the book id's and trade id's at some future random number,
// looks more realistic than starting them at 0
let nextBookId = 62472;
let nextTradeId = 24287;
let nextBatchId = 101;

// simple value getter, however we can see how many times it gets called. this
// gives us an indication to how many rows get recalculated when data changes
const changeValueGetter = (params: ValueGetterParams) => {
  return params.data.previous - params.data.current;
};

// build up the test data
const createRowData = () => {
  const data = [];
  const thisBatch = nextBatchId++;
  for (let i = 0; i < products.length; i++) {
    const product = products[i];
    productToPortfolioToBooks[product] = {};
    for (let j = 0; j < portfolios.length; j++) {
      const portfolio = portfolios[j];
      productToPortfolioToBooks[product][portfolio] = [];
      const bookCount = randomBetween(MAX_BOOK_COUNT, MIN_BOOK_COUNT);
      for (let k = 0; k < bookCount; k++) {
        const book = createBookName();
        productToPortfolioToBooks[product][portfolio].push(book);
        const tradeCount = randomBetween(MAX_TRADE_COUNT, MIN_TRADE_COUNT);
        for (let l = 0; l < tradeCount; l++) {
          const trade = createTradeRecord(product, portfolio, book, thisBatch);
          data.push(trade);
        }
      }
    }
  }
  return data;
};

const randomBetween = (min: number, max: number) => {
  return Math.floor(window.agRandom() * (max - min + 1)) + min;
};

const createTradeRecord = (
  product: string,
  portfolio: string,
  book: string,
  batch: number,
) => {
  const current = Math.floor(window.agRandom() * 100000) + 100;
  const previous = current + Math.floor(window.agRandom() * 10000) - 2000;
  const trade = {
    product: product,
    portfolio: portfolio,
    book: book,
    trade: createTradeId(),
    submitterID: randomBetween(10, 1000),
    submitterDealID: randomBetween(10, 1000),
    dealType: window.agRandom() < 0.2 ? "Physical" : "Financial",
    bidFlag: window.agRandom() < 0.5 ? "Buy" : "Sell",
    current: current,
    previous: previous,
    pl1: randomBetween(100, 1000),
    pl2: randomBetween(100, 1000),
    gainDx: randomBetween(100, 1000),
    sxPx: randomBetween(100, 1000),
    _99Out: randomBetween(100, 1000),
    batch: batch,
  };
  return trade;
};

const numberCellFormatter = (params: ValueFormatterParams) => {
  return Math.floor(params.value)
    .toString()
    .replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
};

const createBookName = () => {
  nextBookId++;
  return "GL-" + nextBookId;
};

const createTradeId = () => {
  nextTradeId++;
  return nextTradeId;
};

const updateSomeItems = (rowData: any) => {
  const updateCount = randomBetween(1, 6);
  for (let k = 0; k < updateCount; k++) {
    if (rowData.length === 0) {
      continue;
    }
    const indexToUpdate = Math.floor(window.agRandom() * rowData.length);
    const itemToUpdate = rowData[indexToUpdate];
    // make a copy of the item, and make some changes, so we are behaving
    // similar to how the
    const updatedItem = updateImmutableObject(itemToUpdate, {
      previous: itemToUpdate.current,
      current: itemToUpdate.current + randomBetween(0, 1000) - 500,
    });
    rowData[indexToUpdate] = updatedItem;
  }
};

const addSomeItems = (rowData: any) => {
  const addCount = randomBetween(1, 6);
  const batch = nextBatchId++;
  for (let j = 0; j < addCount; j++) {
    const portfolio = portfolios[Math.floor(window.agRandom() * portfolios.length)];
    const books = productToPortfolioToBooks["Palm Oil"][portfolio];
    const book = books[Math.floor(window.agRandom() * books.length)];
    const product = products[Math.floor(window.agRandom() * products.length)];
    const trade = createTradeRecord(product, portfolio, book, batch);
    rowData.push(trade);
  }
};

const removeSomeItems = (rowData: any) => {
  const removeCount = randomBetween(1, 6);
  for (let i = 0; i < removeCount; i++) {
    if (rowData.length === 0) {
      continue;
    }
    const indexToRemove = randomBetween(0, rowData.length);
    rowData.splice(indexToRemove, 1);
  }
};

// makes a copy of the original and merges in the new values
const updateImmutableObject = (original: any, newValues: any) => {
  // start with new object
  const newObject: any = {};
  // copy in the old values
  Object.keys(original).forEach(function (key) {
    newObject[key] = original[key];
  });
  // now override with the new values
  Object.keys(newValues).forEach(function (key) {
    newObject[key] = newValues[key];
  });
  return newObject;
};

const rowSelection: RowSelectionOptions = {
  mode: "multiRow",
  groupSelects: "descendants",
  headerCheckbox: false,
};

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

  // a list of the data, that we modify as we go. if you are using an immutable
  // data store (such as Redux) then this would be similar to your store of data.
  const [globalRowData, setGlobalData] = useState<any[]>(createRowData());

  const [rowData, setRowData] = useState<any[]>();
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    // these are the row groups, so they are all hidden (they are showd in the group column)
    {
      field: "product",
      enableRowGroup: true,
      rowGroupIndex: 0,
      hide: true,
    },
    {
      field: "portfolio",
      enableRowGroup: true,
      rowGroupIndex: 1,
      hide: true,
    },
    {
      field: "book",
      enableRowGroup: true,
      rowGroupIndex: 2,
      hide: true,
    },
    { field: "trade", width: 100 },
    // all the other columns (visible and not grouped)
    {
      field: "batch",
      width: 100,
      cellClass: "number",
      aggFunc: "max",
      enableValue: true,
      cellRenderer: "agAnimateShowChangeCellRenderer",
    },
    {
      field: "current",
      width: 200,
      aggFunc: "sum",
      enableValue: true,
      cellClass: "number",
      valueFormatter: numberCellFormatter,
      cellRenderer: "agAnimateShowChangeCellRenderer",
    },
    {
      field: "previous",
      width: 200,
      aggFunc: "sum",
      enableValue: true,
      cellClass: "number",
      valueFormatter: numberCellFormatter,
      cellRenderer: "agAnimateShowChangeCellRenderer",
    },
    {
      headerName: "Change",
      valueGetter: changeValueGetter,
      width: 200,
      aggFunc: "sum",
      enableValue: true,
      cellClass: "number",
      valueFormatter: numberCellFormatter,
      cellRenderer: "agAnimateShowChangeCellRenderer",
    },
    {
      headerName: "PL 1",
      field: "pl1",
      width: 200,
      aggFunc: "sum",
      enableValue: true,
      cellClass: "number",
      valueFormatter: numberCellFormatter,
      cellRenderer: "agAnimateShowChangeCellRenderer",
    },
    {
      headerName: "PL 2",
      field: "pl2",
      width: 200,
      aggFunc: "sum",
      enableValue: true,
      cellClass: "number",
      valueFormatter: numberCellFormatter,
      cellRenderer: "agAnimateShowChangeCellRenderer",
    },
    {
      headerName: "Gain-DX",
      field: "gainDx",
      width: 200,
      aggFunc: "sum",
      enableValue: true,
      cellClass: "number",
      valueFormatter: numberCellFormatter,
      cellRenderer: "agAnimateShowChangeCellRenderer",
    },
    {
      headerName: "SX / PX",
      field: "sxPx",
      width: 200,
      aggFunc: "sum",
      enableValue: true,
      cellClass: "number",
      valueFormatter: numberCellFormatter,
      cellRenderer: "agAnimateShowChangeCellRenderer",
    },
    {
      headerName: "99 Out",
      field: "_99Out",
      width: 200,
      aggFunc: "sum",
      enableValue: true,
      cellClass: "number",
      valueFormatter: numberCellFormatter,
      cellRenderer: "agAnimateShowChangeCellRenderer",
    },
    {
      headerName: "Submitter ID",
      field: "submitterID",
      width: 200,
      aggFunc: "sum",
      enableValue: true,
      cellClass: "number",
      valueFormatter: numberCellFormatter,
      cellRenderer: "agAnimateShowChangeCellRenderer",
    },
    {
      headerName: "Submitted Deal ID",
      field: "submitterDealID",
      width: 200,
      aggFunc: "sum",
      enableValue: true,
      cellClass: "number",
      valueFormatter: numberCellFormatter,
      cellRenderer: "agAnimateShowChangeCellRenderer",
    },
    // some string values, that do not get aggregated
    {
      field: "dealType",
      enableRowGroup: true,
      filter: "agTextColumnFilter",
    },
    {
      headerName: "Bid",
      field: "bidFlag",
      enableRowGroup: true,
      width: 100,
      filter: "agTextColumnFilter",
    },
    { field: "comment", editable: true, filter: "agTextColumnFilter" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      width: 120,
      filter: "agNumberColumnFilter",
    };
  }, []);
  const autoGroupColumnDef = useMemo(() => {
    return {
      width: 250,
      field: "trade",
    };
  }, []);
  const getRowId = useCallback(function (params: GetRowIdParams) {
    return String(params.data.trade);
  }, []);

  const updateData = useCallback(() => {
    const rowData = globalRowData.splice(0);
    removeSomeItems(rowData);
    addSomeItems(rowData);
    updateSomeItems(rowData);
    setGlobalData(rowData);
  }, [globalRowData]);

  // update rowData when our "global store" updates
  useEffect(() => setRowData(globalRowData), [globalRowData]);

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div className="example-wrapper">
          <div style={{ marginBottom: "5px" }}>
            <button onClick={updateData}>Update</button>
          </div>

          <div style={gridStyle}>
            <AgGridReact
              rowData={rowData}
              columnDefs={columnDefs}
              defaultColDef={defaultColDef}
              autoGroupColumnDef={autoGroupColumnDef}
              rowSelection={rowSelection}
              suppressAggFuncInHeader={true}
              getRowId={getRowId}
            />
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Complex Immutable Store](https://www.ag-grid.com/examples/data-update-row-data/complex-immutable-store/reactFunctionalTs)

## Comparison to Transaction Updates

When setting Row Data and not providing Row IDs, the grid rips all data out of the grid and starts from scratch with the new Row Data.

However when providing Row IDs and updating Row Data, the grid creates a [Transaction Update](https://www.ag-grid.com/react-data-grid/data-update-transactions/) underneath the hood. In other words, once the grid has worked out what rows have been added, updated and removed, it then creates a transaction with these details and applies it. This means all the operational benefits to Transaction Updates equally apply to setting Row Data with providing Row IDs.

There are however some differences with updating Row Data (with Row IDs) and Transaction Updates. These differences are as follows:

- When setting Row Data, the grid will have the overhead of identifying what rows are added, removed and updated.
- When setting Row Data, the grid stores the data in the same order as the data was provided. For example if you provide a new list with data added in the middle of the list, the grid will also put the data into the middle of the list rather than just appending to the end. This decides the order of data when there is no grid sort applied. If this is not required by your application, then you can suppress this behaviour for a performance boost by setting `suppressMaintainUnsortedOrder=true` in the [Grid Options](https://www.ag-grid.com/react-data-grid/grid-options/#reference-sort-suppressMaintainUnsortedOrder).
- There is no equivalent of [Async Transactions](https://www.ag-grid.com/react-data-grid/data-update-high-frequency/) when it comes to updating Row Data. If you want a grid that manages high frequency data changes, do not update Row Data directly, use [Async Transactions](https://www.ag-grid.com/react-data-grid/data-update-high-frequency/) instead.

For the reasons mentioned above, if you have large data sets (thousands of rows) and are looking for ways to make things go faster, consider using [Transaction Update](https://www.ag-grid.com/react-data-grid/data-update-transactions/).

If you have smaller data sets (hundreds of rows) then everything should work without any noticeable lag.

## Adding New Rows

Adding additional data rows by the end users is not supported built-in. However the desired behaviour can be achieved with custom code. One method of achieving this is shown in the example below using [Full Row Edit](https://www.ag-grid.com/react-data-grid/cell-editing-full-row/) and [Row Pinning](https://www.ag-grid.com/react-data-grid/row-pinning/).

#### Add Rows to Immutable Store

```tsx
'use client';
import React, {
  StrictMode,
  useCallback,
  useMemo,
  useRef,
  useState,
} from "react";
import { createRoot } from "react-dom/client";

import type {
  ColDef,
  EditableCallbackParams,
  GetRowIdParams,
  RowEditingStoppedEvent,
} from "ag-grid-community";
import {
  ClientSideRowModelModule,
  ColumnApiModule,
  NumberEditorModule,
  PinnedRowModule,
  TextEditorModule,
  enableDevValidations,
} from "ag-grid-community";
import { AgGridProvider, AgGridReact } from "ag-grid-react";

import { getData } from "./data";

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

const modules = [
  ColumnApiModule,
  ClientSideRowModelModule,
  TextEditorModule,
  NumberEditorModule,
  PinnedRowModule,
];

const GridExample = () => {
  const gridRef = useRef<AgGridReact>(null);
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [rowData, setRowData] = useState(getData());
  const [pinnedBottomRowData, setPinnedBottomRowData] = useState([]);
  const [columnDefs, setColumnDefs] = useState<ColDef[]>([
    { headerName: "Symbol", field: "symbol" },
    { headerName: "Price", field: "price" },
    { headerName: "Group", field: "group" },
  ]);
  const defaultColDef = useMemo<ColDef>(() => {
    return {
      width: 250,
      editable: (params: EditableCallbackParams) => {
        return params.node.id === "new-row";
      },
    };
  }, []);

  const getRowId = useCallback(function (params: GetRowIdParams) {
    return params.data.symbol ?? "new-row";
  }, []);

  const addNewRow = useCallback(() => {
    const { api } = gridRef.current || {};

    if (!api) {
      return;
    }

    api.setGridOption("pinnedBottomRowData", [
      { symbol: null, price: null, group: null },
    ]);
    setTimeout(() => {
      api.startEditingCell({
        rowIndex: 0,
        rowPinned: "bottom",
        colKey: "symbol",
      });
    });
  }, []);

  const onRowEditingStopped = useCallback(
    (params: RowEditingStoppedEvent) => {
      const { data } = params;

      setPinnedBottomRowData([]);

      if (data.symbol == null) {
        return;
      }

      setRowData([data, ...rowData]);
    },
    [rowData],
  );

  return (
    <AgGridProvider modules={modules}>
      <div style={containerStyle}>
        <div
          style={{
            height: "100%",
            width: "100%",
            display: "flex",
            flexDirection: "column",
          }}
        >
          <div>
            <div style={{ marginBottom: "5px", minHeight: "30px" }}>
              <button onClick={addNewRow}>Add New Row</button>
            </div>
          </div>
          <div style={{ flex: "1 1 0px" }}>
            <div style={gridStyle}>
              <AgGridReact
                ref={gridRef}
                rowData={rowData}
                columnDefs={columnDefs}
                defaultColDef={defaultColDef}
                editType={"fullRow"}
                getRowId={getRowId}
                pinnedBottomRowData={pinnedBottomRowData}
                onRowEditingStopped={onRowEditingStopped}
              />
            </div>
          </div>
        </div>
      </div>
    </AgGridProvider>
  );
};

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

[Live example: Add Rows to Immutable Store](https://www.ag-grid.com/examples/data-update-row-data/add-rows-to-immutable-store/reactFunctionalTs)
