---
title: "Updating Row Data"
framework: vue
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

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowSelectionModule,
  RowSelectionOptions,
  enableDevValidations,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  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 VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="height: 100%; width: 100%; display: flex; flex-direction: column">
      <div style="margin-bottom: 5px; min-height: 30px">
        <button v-on:click="onRowDataA()">Row Data A</button>
        <button v-on:click="onRowDataB()">Row Data B</button>
        <button v-on:click="onClearRowData()">Clear Row Data</button>
      </div>
      <div style="flex: 1 1 0px">
        <ag-grid-vue
          style="width: 100%; height: 100%;"
          @grid-ready="onGridReady"
          :columnDefs="columnDefs"
          :rowData="rowData"
          :rowSelection="rowSelection"></ag-grid-vue>
        </div>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<ICar> | null>(null);
    const columnDefs = ref<ColDef[]>([
      { field: "make" },
      { field: "model" },
      { field: "price" },
    ]);
    const rowData = ref<ICar[] | null>(rowDataA);
    const rowSelection = ref<RowSelectionOptions | "single" | "multiple">({
      mode: "singleRow",
      checkboxes: false,
      enableClickSelection: true,
    });

    function onRowDataA() {
      gridApi.value!.setGridOption("rowData", rowDataA);
    }
    function onRowDataB() {
      gridApi.value!.setGridOption("rowData", rowDataB);
    }
    function onClearRowData() {
      // Clear rowData by setting it to an empty array
      gridApi.value!.setGridOption("rowData", []);
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      rowData,
      rowSelection,
      onGridReady,
      onRowDataA,
      onRowDataB,
      onClearRowData,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

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

The example below is identical to the above except [Row IDs](https://www.ag-grid.com/vue-data-grid/row-ids/) are provided via the `getRowId()` callback. This results in [Row Selection](https://www.ag-grid.com/vue-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/vue-data-grid/row-animation/).

#### Simple Row ID

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowSelectionModule,
  RowSelectionOptions,
  enableDevValidations,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  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 VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="height: 100%; width: 100%; display: flex; flex-direction: column">
      <div style="margin-bottom: 5px; min-height: 30px">
        <button v-on:click="onRowDataA()">Row Data A</button>
        <button v-on:click="onRowDataB()">Row Data B</button>
        <button v-on:click="onClearRowData()">Clear Row Data</button>
      </div>
      <div style="flex: 1 1 0px">
        <ag-grid-vue
          style="width: 100%; height: 100%;"
          @grid-ready="onGridReady"
          :columnDefs="columnDefs"
          :rowData="rowData"
          :rowSelection="rowSelection"
          :getRowId="getRowId"></ag-grid-vue>
        </div>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<ICar> | null>(null);
    const columnDefs = ref<ColDef[]>([
      { field: "make" },
      { field: "model" },
      { field: "price" },
    ]);
    const rowData = ref<ICar[] | null>(rowDataA);
    const rowSelection = ref<RowSelectionOptions | "single" | "multiple">({
      mode: "singleRow",
      checkboxes: false,
      enableClickSelection: true,
    });
    const getRowId = ref<GetRowIdFunc>(
      (params: GetRowIdParams<ICar>) => params.data.id,
    );

    function onRowDataA() {
      gridApi.value!.setGridOption("rowData", rowDataA);
    }
    function onRowDataB() {
      gridApi.value!.setGridOption("rowData", rowDataB);
    }
    function onClearRowData() {
      gridApi.value!.setGridOption("rowData", []);
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      rowData,
      rowSelection,
      getRowId,
      onGridReady,
      onRowDataA,
      onRowDataB,
      onClearRowData,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

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

Providing [Row IDs](https://www.ag-grid.com/vue-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/vue-data-grid/row-selection/) | Row Selection lost | Row Selection maintained |
| [Row Grouping](https://www.ag-grid.com/vue-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/vue-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/vue-data-grid/row-animation/) | No row animation | Moved rows animate to new position |
| [Flashing Cells](https://www.ag-grid.com/vue-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/vue-data-grid/grouping/), [Cell Selection](https://www.ag-grid.com/vue-data-grid/cell-selection/) and [Status Bar](https://www.ag-grid.com/vue-data-grid/status-bar/).)

#### Simple Immutable Store

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  AutoGroupColumnDef,
  CellSelectionOptions,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  ColumnApiModule,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowSelectionModule,
  RowSelectionOptions,
  StatusBar,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  CellSelectionModule,
  RowGroupingModule,
  StatusBarModule,
} from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ColumnApiModule,
  TextFilterModule,
  RowSelectionModule,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  CellSelectionModule,
  RowGroupingModule,
  StatusBarModule,
]);

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

let immutableStore: any[] = [];

function filter(list: any[], callback: any) {
  const filteredList: any[] = [];
  list.forEach((item) => {
    if (callback(item)) {
      filteredList.push(item);
    }
  });
  return filteredList;
}

function createItem() {
  const item = {
    group: ["A", "B", "C"][Math.floor(window.agRandom() * 3)],
    symbol: createUniqueRandomSymbol(),
    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";
}

// creates a unique symbol, eg 'ADG' or 'ZJD'
function createUniqueRandomSymbol() {
  let symbol: any;
  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;
    immutableStore.forEach((oldItem) => {
      if (oldItem.symbol === symbol) {
        isUnique = false;
      }
    });
  }
  return symbol;
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="height: 100%; width: 100%; display: flex; flex-direction: column">
      <div>
        <div style="margin-bottom: 5px; min-height: 30px">
          <button v-on:click="reverseItems()">Reverse</button>
          <button v-on:click="addFiveItems(true)">Append</button>
          <button v-on:click="addFiveItems(false)">Prepend</button>
          <button v-on:click="removeSelected()">Remove Selected</button>
          <button v-on:click="updatePrices()">Update Prices</button>
        </div>
        <div style="margin-bottom: 5px; min-height: 30px">
          <button id="groupingOn" v-on:click="onGroupingEnabled(true)">Grouping On</button>
          <button id="groupingOff" v-on:click="onGroupingEnabled(false)">Grouping Off</button>
          <button v-on:click="setSelectedToGroup('A')">Move to Group A</button>
          <button v-on:click="setSelectedToGroup('B')">Move to Group B</button>
          <button v-on:click="setSelectedToGroup('C')">Move to Group C</button>
        </div>
      </div>
      <div style="flex: 1 1 0px">
        <ag-grid-vue
          style="width: 100%; height: 100%;"
          @grid-ready="onGridReady"
          :columnDefs="columnDefs"
          :defaultColDef="defaultColDef"
          :rowSelection="rowSelection"
          :cellSelection="true"
          :autoGroupColumnDef="autoGroupColumnDef"
          :statusBar="statusBar"
          :groupDefaultExpanded="groupDefaultExpanded"
          :rowData="rowData"
          :getRowId="getRowId"></ag-grid-vue>
        </div>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      { headerName: "Symbol", field: "symbol" },
      { headerName: "Price", field: "price" },
      { headerName: "Group", field: "group" },
    ]);
    const defaultColDef = ref<ColDef>({
      width: 250,
    });
    const rowSelection = ref<RowSelectionOptions | "single" | "multiple">({
      mode: "multiRow",
    });
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      headerName: "Symbol",
      cellRenderer: "agGroupCellRenderer",
      field: "symbol",
    });
    const statusBar = ref<StatusBar>({
      statusPanels: [{ statusPanel: "agAggregationComponent", align: "right" }],
    });
    const groupDefaultExpanded = ref(1);
    const rowData = ref<any[] | null>(immutableStore);
    const getRowId = ref<GetRowIdFunc>((params: GetRowIdParams) => {
      return params.data.symbol;
    });

    function addFiveItems(append: boolean) {
      const newStore = immutableStore.slice();
      for (let i = 0; i < 5; i++) {
        const newItem = createItem();
        if (append) {
          newStore.push(newItem);
        } else {
          newStore.splice(0, 0, newItem);
        }
      }
      immutableStore = newStore;
      gridApi.value!.setGridOption("rowData", immutableStore);
    }
    function removeSelected() {
      const selectedRowNodes = gridApi.value!.getSelectedNodes();
      const selectedIds = selectedRowNodes.map(function (rowNode) {
        return rowNode.id;
      });
      immutableStore = immutableStore.filter(function (dataItem) {
        return selectedIds.indexOf(dataItem.symbol) < 0;
      });
      gridApi.value!.setGridOption("rowData", immutableStore);
    }
    function setSelectedToGroup(newGroup: string) {
      const selectedRowNodes = gridApi.value!.getSelectedNodes();
      const selectedIds = selectedRowNodes.map(function (rowNode) {
        return rowNode.id;
      });
      immutableStore = immutableStore.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;
        }
      });
      gridApi.value!.setGridOption("rowData", immutableStore);
    }
    function updatePrices() {
      const newStore: any[] = [];
      immutableStore.forEach((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),
        });
      });
      immutableStore = newStore;
      gridApi.value!.setGridOption("rowData", immutableStore);
    }
    function onGroupingEnabled(enabled: boolean) {
      setGroupingEnabled(enabled, gridApi.value!);
    }
    function reverseItems() {
      immutableStore.reverse();
      gridApi.value!.setGridOption("rowData", immutableStore);
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      immutableStore = [];
      immutableStore = getInitialData();
      params.api.setGridOption("rowData", immutableStore);
      setGroupingEnabled(false, params.api);
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      rowSelection,
      autoGroupColumnDef,
      statusBar,
      groupDefaultExpanded,
      rowData,
      getRowId,
      onGridReady,
      addFiveItems,
      removeSelected,
      setSelectedToGroup,
      updatePrices,
      onGroupingEnabled,
      reverseItems,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

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

## 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

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  AutoGroupColumnDef,
  CellStyleModule,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  HighlightChangesModule,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  RowSelectionOptions,
  TextEditorModule,
  TextFilterModule,
  ValueFormatterParams,
  ValueGetterParams,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  TextEditorModule,
  RowSelectionModule,
  CellStyleModule,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  RowGroupingModule,
  HighlightChangesModule,
  TextFilterModule,
  NumberFilterModule,
]);

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
function changeValueGetter(params: ValueGetterParams) {
  return params.data.previous - params.data.current;
}

// 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.
let globalRowData: any[] = [];

// build up the test data
function createRowData() {
  globalRowData = [];
  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);
          globalRowData.push(trade);
        }
      }
    }
  }
}

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

function createTradeRecord(
  product: any,
  portfolio: any,
  book: any,
  batch: any,
) {
  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;
}

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

function createBookName() {
  nextBookId++;
  return "GL-" + nextBookId;
}

function createTradeId() {
  nextTradeId++;
  return nextTradeId;
}

function updateSomeItems() {
  const updateCount = randomBetween(1, 6);
  const itemsToUpdate = [];
  for (let k = 0; k < updateCount; k++) {
    if (globalRowData.length === 0) {
      continue;
    }
    const indexToUpdate = Math.floor(window.agRandom() * globalRowData.length);
    const itemToUpdate = globalRowData[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,
    });
    globalRowData[indexToUpdate] = updatedItem;
    itemsToUpdate.push(updatedItem);
  }
  return itemsToUpdate;
}

function addSomeItems() {
  const addCount = randomBetween(1, 6);
  const itemsToAdd = [];
  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);
    itemsToAdd.push(trade);
    globalRowData.push(trade);
  }
  return itemsToAdd;
}

function removeSomeItems() {
  const removeCount = randomBetween(1, 6);
  const itemsToRemove = [];
  for (let i = 0; i < removeCount; i++) {
    if (globalRowData.length === 0) {
      continue;
    }
    const indexToRemove = randomBetween(0, globalRowData.length);
    const itemToRemove = globalRowData[indexToRemove];
    globalRowData.splice(indexToRemove, 1);
    itemsToRemove.push(itemToRemove);
  }
  return itemsToRemove;
}

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

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <div style="margin-bottom: 5px">
        <button v-on:click="updateData()">Update</button>
      </div>
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :columnDefs="columnDefs"
        :defaultColDef="defaultColDef"
        :autoGroupColumnDef="autoGroupColumnDef"
        :rowSelection="rowSelection"
        :rowData="rowData"
        :suppressAggFuncInHeader="true"
        :getRowId="getRowId"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<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,
      },
      // 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 = ref<ColDef>({
      width: 120,
      filter: "agNumberColumnFilter",
    });
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      width: 250,
      field: "trade",
    });
    const rowSelection = ref<RowSelectionOptions | "single" | "multiple">({
      mode: "multiRow",
      groupSelects: "descendants",
      headerCheckbox: false,
    });
    const rowData = ref<any[] | null>(globalRowData);
    const getRowId = ref<GetRowIdFunc>((params: GetRowIdParams) =>
      String(params.data.trade),
    );

    function updateData() {
      removeSomeItems();
      addSomeItems();
      updateSomeItems();
      gridApi.value!.setGridOption("rowData", globalRowData);
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      createRowData();
      params.api.setGridOption("rowData", globalRowData);
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      autoGroupColumnDef,
      rowSelection,
      rowData,
      getRowId,
      onGridReady,
      updateData,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

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

## 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/vue-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/vue-data-grid/grid-options/#reference-sort-suppressMaintainUnsortedOrder).
- There is no equivalent of [Async Transactions](https://www.ag-grid.com/vue-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/vue-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/vue-data-grid/data-update-transactions/).

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

## Two Way Binding

By default, `:rowData` is a **one-way binding**: data flows into the grid, but changes made within the grid (e.g. via cell editing) will **not** propagate back to the parent component's `rowData` variable.

To have row data changes flow back up from the grid to the parent component, use `v-model` instead of `:rowData`.

For example:

```jsx
<template>
    <ag-grid-vue style="width: 500px; height: 500px;"
                 @grid-ready="onGridReady"
                 :columnDefs="columnDefs"
                 v-model="rowData">
    </ag-grid-vue>
</template>
```

> **Note**
>
> `v-model` is only possible when `ClientSideRowModel` is used, and either the `AllCommunityModule` or the `ClientSideRowModelApiModule` module is registered.

## 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/vue-data-grid/cell-editing-full-row/) and [Row Pinning](https://www.ag-grid.com/vue-data-grid/row-pinning/).

#### Add Rows to Immutable Store

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  ColumnApiModule,
  EditStrategyType,
  EditableCallbackParams,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  NumberEditorModule,
  PinnedRowModule,
  RowEditingStoppedEvent,
  TextEditorModule,
  enableDevValidations,
} from "ag-grid-community";
import { getData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ColumnApiModule,
  ClientSideRowModelModule,
  TextEditorModule,
  NumberEditorModule,
  PinnedRowModule,
]);

let immutableStore: any[] = [];

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="height: 100%; width: 100%; display: flex; flex-direction: column">
      <div>
        <div style="margin-bottom: 5px; min-height: 30px">
          <button v-on:click="addNewRow()">Add New Row</button>
        </div>
      </div>
      <div style="flex: 1 1 0px">
        <ag-grid-vue
          style="width: 100%; height: 100%;"
          @grid-ready="onGridReady"
          :columnDefs="columnDefs"
          :defaultColDef="defaultColDef"
          :editType="editType"
          :rowData="rowData"
          :getRowId="getRowId"
          @row-editing-stopped="onRowEditingStopped"></ag-grid-vue>
        </div>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      { headerName: "Symbol", field: "symbol" },
      { headerName: "Price", field: "price" },
      { headerName: "Group", field: "group" },
    ]);
    const defaultColDef = ref<ColDef>({
      width: 250,
      editable: (params: EditableCallbackParams) => {
        return params.node.id === "new-row";
      },
    });
    const editType = ref<EditStrategyType>("fullRow");
    const rowData = ref<any[] | null>(immutableStore);
    const getRowId = ref<GetRowIdFunc>((params: GetRowIdParams) => {
      return params.data.symbol ?? "new-row";
    });

    function onRowEditingStopped(params: RowEditingStoppedEvent) {
      const { data } = params;
      gridApi.value!.setGridOption("pinnedBottomRowData", []);
      if (data.symbol == null) {
        return;
      }
      immutableStore = [data, ...immutableStore];
      gridApi.value!.setGridOption("rowData", immutableStore);
    }
    function addNewRow() {
      gridApi.value!.setGridOption("pinnedBottomRowData", [
        { symbol: null, price: null, group: null },
      ]);
      gridApi.value!.startEditingCell({
        rowIndex: 0,
        rowPinned: "bottom",
        colKey: "symbol",
      });
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      immutableStore = getData();
      params.api.setGridOption("rowData", immutableStore);
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      editType,
      rowData,
      getRowId,
      onGridReady,
      onRowEditingStopped,
      addNewRow,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

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