# Deleting Rows in Dash AG Grid

**URL:** <https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700>\
**Category:** Dash Python\
**Tags:** tips-and-tricks\
**Created:** [September 21, 2023, 4:26pm UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700 "2023-09-21T16:26:42Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![AnnMarieW](https://avatars.discourse-cdn.com/v4/letter/a/ec9cab/32.png) [@AnnMarieW](https://community.plotly.com/u/AnnMarieW)\
**Post date:** [September 21, 2023, 4:26pm UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700/1 "2023-09-21T16:26:43Z")

</div>

# Deleting Rows in Dash AG Grid

This question came up in another topic, so I decided to make it a separate post.

Here are several different ways to delete rows in Dash AG Grid. This post will demo:

1. Using the `deleteSelectedRows` prop in a callback
2. Creating a column of delete buttons ( like in DataTable)
3. Using `rowTransactions` prop in a callback.

## 1. Delete selected rows button

This method uses a button to delete selected rows in a callback, It makes it easy for the user to delete multiple rows. See the the post on all the different ways you can [select rows in Dash AG Grid](https://community.plotly.com/t/selected-rows-in-dash-ag-grid/78580).

You simply create a grid with selectable rows, then have a button that triggers a callback to sent to the `deleteSelectedRows` prop to `True`.

![ag-grid-delete-selected-rows](https://us1.discourse-cdn.com/flex024/uploads/plot/original/3X/a/1/a1e41d395f5cd342dd037e35dc01d3bc0326e5f2.gif)

```auto

import dash_ag_grid as dag
from dash import Dash, html, Input, Output, callback
import pandas as pd

app = Dash( __name__ )

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

columnDefs = [
    {"field": "athlete", "checkboxSelection": True, "headerCheckboxSelection": True},
    {"field": "age", "maxWidth": 100},
    {"field": "country"},
    {"field": "year", "maxWidth": 120},
    {"field": "date"},
    {"field": "sport"},
    {"field": "total"},
]

defaultColDef = { "flex": 1, "minWidth": 150, "sortable": True, "resizable": True, "filter": True}

app.layout = html.Div(
    [
        html.Button("Delete Selected", id="button"),
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            rowData=df.to_dict("records"),
            defaultColDef=defaultColDef,
            dashGridOptions={"rowSelection":"multiple"},
        ),
    ],
    style={"margin": 20},
)

@callback(
    Output("grid", "deleteSelectedRows"),
    Input("button", "n_clicks"),
    prevent_initial_call=True
)
def selected(_):
    return True

```

## 2. Delete button component in a cell (like Dash DataTable)

See more information on using components in cells in the [Dash AG Grid docs](https://dash.plotly.com/dash-ag-grid/cell-renderer-components)

![ag-grid-delete-row-like-datatable](https://us1.discourse-cdn.com/flex024/uploads/plot/original/3X/c/1/c11fc37277d6079cc2cf5eecdd8ede25152b5da9.gif)

```auto

import dash_ag_grid as dag
from dash import Dash, html
import pandas as pd

app = Dash( __name__ )

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)
df=df.head(10)

columnDefs = [
    {
        "headerName": "",
        "cellRenderer": "DeleteButton",
        "lockPosition":'left',
        "maxWidth":35,
        "filter": False,
        'cellStyle': {'paddingRight': 0, 'paddingLeft': 0},
    },
    {"field": "athlete", "checkboxSelection": True, "headerCheckboxSelection": True},
    {"field": "age", "maxWidth": 100},
    {"field": "country"},
    {"field": "year", "maxWidth": 120},
    {"field": "date"},
    {"field": "sport"},
    {"field": "total"},
]

defaultColDef = { "sortable": True, "resizable": True, "filter": True}

app.layout = html.Div(
    [
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            rowData=df.to_dict("records"),
            defaultColDef=defaultColDef,
            dashGridOptions={"rowSelection": "multiple", "suppressRowClickSelection": True},
        ),
    ],
    style={"margin": 20},
)

if __name__ == " __main__":
    app.run(debug=True)

"""

Place the following in the dashAgGridComponentFunctions.js file in the assets folder

---------------

var dagcomponentfuncs = window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {};

dagcomponentfuncs.DeleteButton = function (props) {
    function onClick() {
          props.api.applyTransaction({ remove: [props.node.data] })
    }
    return React.createElement('button', {onClick}, "X");
};

"""

```

## 3. Delete Rows in a callback using rowTransaction

An efficient way to update data in the grid (including deleting rows) is to use [rowTransaction](https://dash.plotly.com/dash-ag-grid/client-side-data-updating)

The rows must have [row ids](https://dash.plotly.com/dash-ag-grid/row-ids) and It’s only necessary to specify the ids to delete.

This example shows how to delete rows when the grid does not have selectable rows. To illustrate, it uses a button to delete all rows where the year is 2012.

```auto

import dash_ag_grid as dag
from dash import Dash, html, Input, Output, callback
import pandas as pd

app = Dash( __name__ )

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)
df["id"] = df.index

defaultColDef = {"flex": 1, "minWidth": 150, "sortable": True, "resizable": True, "filter": True}

app.layout = html.Div(
    [
        html.Button("Delete 2012", id="year"),
        html.Button("Undo", id="undo"),
        dag.AgGrid(
            id="grid",
            columnDefs=[{"field": i} for i in df.columns],
            rowData=df.to_dict("records"),
            defaultColDef=defaultColDef,
            getRowId="params.data.id",
        ),
    ],
    style={"margin": 20},
)

@callback(
    Output("grid", "rowTransaction"),
    Input("year", "n_clicks"),
    prevent_intial_call=True,
)
def delete_rows(_):
    dff = df[df['year'] == 2012]

    # it's only necessary to include the row ids
    ids = dff[["id"]]
    return {"remove": ids.to_dict("records")}

@callback(
    Output("grid", "rowData"),
    Input("undo", "n_clicks"),
    prevent_intial_call=True,
)
def undo(_):
   return df.to_dict("records")

if __name__ == " __main__":
    app.run(debug=True)

```

---

<div class="post-metadata">

**Author:** ![RenaudLN](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/renaudln/32/9041_2.png) [@RenaudLN](https://community.plotly.com/u/RenaudLN)\
**Post date:** [November 24, 2023, 3:19am UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700/3 "2023-11-24T03:19:01Z")

</div>

Hey @AnnMarieW , love the examples here 🙂

I implemented the one with the button on the row and it works great for updating the table, however it doesn’t seem to be updating any Dash properties to trigger a callback when the row is removed. Do you have an idea of how to go about it?

---

<div class="post-metadata">

**Author:** ![jinnyzor](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/jinnyzor/32/21194_2.png) [@jinnyzor](https://community.plotly.com/u/jinnyzor)\
**Post date:** [November 24, 2023, 5:24am UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700/4 "2023-11-24T05:24:48Z")

</div>

Hello @RenaudLN,

What happens if you pass `“async”: False` in the `rowTransaction`? Does the `rowData` update in dash?

---

<div class="post-metadata">

**Author:** ![RenaudLN](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/renaudln/32/9041_2.png) [@RenaudLN](https://community.plotly.com/u/RenaudLN)\
**Post date:** [November 24, 2023, 5:50am UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700/5 "2023-11-24T05:50:24Z")

</div>

I tried the following but no update to rowData

```javascript
props.api.applyTransaction({ remove: [props.node.data], async: false })

```

---

<div class="post-metadata">

**Author:** ![jinnyzor](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/jinnyzor/32/21194_2.png) [@jinnyzor](https://community.plotly.com/u/jinnyzor)\
**Post date:** [November 24, 2023, 5:54am UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700/6 "2023-11-24T05:54:54Z")

</div>

Using the underlying api with that is going to be sync, to be async it would be `applyTransactionAsync`, we do this in the `rowTransaction` by using the keyword `async` which defaults to True.

The other thing that will update is `virtualRowData`. Issue here is that it updates for other reasons as well.

---

<div class="post-metadata">

**Author:** ![seanrez](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/seanrez/32/20533_2.png) [@seanrez](https://community.plotly.com/u/seanrez)\
**Post date:** [March 15, 2025, 4:19pm UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700/7 "2025-03-15T16:19:28Z")

</div>

I implemented method number two with the `dagcomponentfuncs.DeleteButton` and it _looks like_ the row/record is being deleted, but when I later access the `rowData`, the deleted row/record is still there, as if it hasn’t been deleted at all.

```javascript

// Delete button for AG Grid
// https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700
dagcomponentfuncs.DeleteButton = function (props) {
  function onClick() {
    props.api.applyTransaction({ remove: [props.node.data] });
  }

  // Red color? "#ff0000"
  colorWanted = props.colorWanted || "";
  
  return React.createElement(
    "span", // Using span instead of button for less default styling
    {
      onClick,
      style: {
        cursor: "pointer", // Show pointer cursor on hover
        color: colorWanted, 
        fontSize: "16px", // Larger font size
        fontWeight: "bold", // Make it bold
        display: "flex", // Center content
        justifyContent: "center", // Center horizontally
        alignItems: "center", // Center vertically
        width: "100%", // Take full width of the cell
        height: "100%", // Take full height of the cell
        transition: "color 0.2s", // Smooth color transition on hover
      },
      onMouseOver: (e) => (e.currentTarget.style.color = "#cc0000"), // Darker red on hover
      onMouseOut: (e) => (e.currentTarget.style.color = colorWanted), // Restore original color
      title: "Delete row", // Tooltip on hover
    },
    "×" // Using the multiplication symbol which looks nicer than "X"
  );
};

```

columnDefs below:

```python
{
            "field": "delete",
            "headerName": "",
            "cellRenderer": "DeleteButton",
            "lockPosition": "left",
            "filter": False,
            "maxWidth": 35,
        }

```

Here’s a fully reproducible example that illustrates the problem of the rows not actually being deleted (for my purposes):

```python
import json
from datetime import date

import dash_bootstrap_components as dbc
import dash_mantine_components as dmc
from dash import (
    Dash,
    Input,
    Output,
    State,
    _dash_renderer,
    callback,
    callback_context,
    html,
)
from dash.exceptions import PreventUpdate
from dash_ag_grid import AgGrid
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.declarative import declarative_base

app = Flask( __name__ )
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:" # In-memory database
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.secret_key = "your-secret-keyyyyyyyyyyyyyyyyyyyy" # Required for Flask session

# Do this before creating Dash app object.
# Dash Mantine Components is based on REACT 18.
# You must set the env variable REACT_VERSION=18.2.0 before starting the app.
# https://www.dash-mantine-components.com/migration
# _available_react_versions = {"16.14.0", "18.2.0"}
_dash_renderer._set_react_version("18.2.0")

dash_app = Dash( __name__ , external_stylesheets=[dbc.themes.BOOTSTRAP], server=app)

db = SQLAlchemy(app=app)
Base = declarative_base()

def get_id_triggered() -> str:
    """Get the ID that triggered the callback"""
    if not callback_context.triggered:
        return ""

    # This changed a bit when I updated the Dash version... Be careful
    return callback_context.triggered[0]["prop_id"]

def get_ag_grid(
    id: str = "",
    rowData: list = [],
    getRowId: str = None,
    defaultColDef: dict = {},
    dashGridOptions: dict = {},
    columnDefs: list = [],
    columnSize: str = "autoSize",
    column_filter: bool = True,
    column_sortable: bool = True,
    pagination: bool = True,
    paginationPageSize: int = 10,
    skipHeaderOnAutoSize: bool = False,
    columnHoverHighlight: bool = True,
    # style: dict = None,
    csv_filename: str = f"IJACK Data {date.today().strftime('%Y-%m-%d')}.csv",
    autoHeight: bool = True,
    # If autoHeight is True, height is forced to None so the below has no effect
    height: str = "70vh",
    className: str = "ag-theme-quartz ag-theme-ijack",
    style: dict | None = None,
) -> AgGrid:
    """Get the default AgGrid component"""

    if autoHeight:
        # height must be None for autoHeight to work
        height = None
        domLayout = "autoHeight"
        paginationAutoPageSize = False
        page_sizes: set = {5, 10, 15, 20, 25, 50, 100}
        page_sizes.add(paginationPageSize)
        paginationPageSizeSelector = list(page_sizes)
        paginationPageSizeSelector.sort()
    else:
        height = height
        domLayout = "normal"
        paginationAutoPageSize = True
        paginationPageSizeSelector = False

    # Inside a div with max-height for the viewport
    return AgGrid(
        id=id,
        rowData=rowData,
        columnDefs=columnDefs,
        getRowId=getRowId,
        # Default theme is "ag-theme-quartz"
        className=className,
        # className="ag-theme-alpine dbc-ag-grid",
        style=style or {"height": height, "width": "100%"},
        dashGridOptions=dashGridOptions
        or {
            "animateRows": True,
            # allow the grid to auto-size its height to fit rows (this is nice sometimes)
            "domLayout": domLayout,
            "rowHeight": 30,
            "skipHeaderOnAutoSize": skipHeaderOnAutoSize,
            # Default tooltip show delay is 2000 ms (2 seconds)
            "tooltipShowDelay": 100,
            "pagination": pagination,
            "paginationPageSize": paginationPageSize,
            "paginationAutoPageSize": paginationAutoPageSize,
            "paginationPageSizeSelector": paginationPageSizeSelector,
            # Highlight the column on hover, not just the row
            "columnHoverHighlight": columnHoverHighlight,
            # Render all popups (including dropdown menus) in the document body
            # instead of inside the grid container, so they will not be cut off by the grid container.
            "popupParent": {"function": "setBody()"},
            # Make filter buttons more touchable
            "suppressMenuHide": True, # Always show filter buttons
            # Enable better touch support
            "suppressTouch": False, # Allow touch interactions
            "rowSelection": "multiple",
            "suppressRowClickSelection": True,
        },
        # https://dash.plotly.com/dash-ag-grid/column-sizing
        # "autoSize" = the grid will fill the width of the container
        # "sizeToFit" = the grid will fill the width of the container, but each column will be auto-sized to fit the data
        # "responsiveSizeToFit" = the grid will fill the width of the container, but each column will be auto-sized to fit the data,
        # and the grid will resize as the container resizes
        columnSize=columnSize,
        defaultColDef=defaultColDef
        or {
            "resizable": True,
            "editable": False,
            "sortable": column_sortable,
            "filter": column_filter,
            "flex": 1,
            "initialWidth": 100,
            "minWidth": 100,
            "maxWidth": 150,
            "wrapHeaderText": True,
            "autoHeaderHeight": True,
            "wrapText": True,
            "autoHeight": True,
            "cellStyle": {
                # Breaks words according to the default line break rules,
                # not 'break-all' which breaks all words on any character.
                "wordBreak": "normal",
                "whiteSpace": "normal",
                "line-height": "1.5",
            },
            "filterParams": {
                "buttons": ["apply", "clear"], # Add apply/clear buttons
                "closeOnApply": True, # Close filter popup after applying
            },
            "sortingOrder": ["desc", "asc", None],
        },
        csvExportParams={
            "allColumns": True,
            "columnSeparator": ",",
            "fileName": csv_filename,
            "skipColumnGroupHeaders": True,
            "skipColumnHeaders": False,
            "skipPinnedBottom": True,
            "skipPinnedTop": True,
            "skipRowGroups": True,
            "suppressQuotes": False,
        },
    )

# Sample data for warehouses - used for the dropdown options
warehouse_options = [
    {"value": "wh1", "label": "Warehouse 1 - NYC"},
    {"value": "wh2", "label": "Warehouse 2 - LA"},
    {"value": "wh3", "label": "Warehouse 3 - Chicago"},
    {"value": "wh4", "label": "Warehouse 4 - Houston"},
    {"value": "wh5", "label": "Warehouse 5 - Miami"},
]

# Initial row data - sample records that will be displayed in the grid
initial_row_data = [
    {
        "delete": "",
        "id": 1,
        "product": "Laptop",
        "quantity": 25,
        "warehouse_id": "wh1",
        "price": 999.99,
    },
    {
        "delete": "",
        "id": 2,
        "product": "Smartphone",
        "quantity": 50,
        "warehouse_id": "wh2",
        "price": 499.99,
    },
    {
        "delete": "",
        "id": 3,
        "product": "Tablet",
        "quantity": 15,
        "warehouse_id": "wh3",
        "price": 349.99,
    },
    {
        "delete": "",
        "id": 4,
        "product": "Monitor",
        "quantity": 30,
        "warehouse_id": "wh4",
        "price": 199.99,
    },
    {
        "delete": "",
        "id": 5,
        "product": "Keyboard",
        "quantity": 100,
        "warehouse_id": "wh5",
        "price": 49.99,
    },
]

# Initial column definitions - this defines the structure of our grid
def get_column_defs():
    return [
        # Delete column with DeleteButton renderer
        {
            "field": "delete",
            "headerName": "",
            "cellRenderer": "DeleteButton",
            "lockPosition": "left",
            "filter": False,
            "maxWidth": 35,
            "cellStyle": {"paddingRight": 0, "paddingLeft": 0},
        },
        # Warehouse selection column with SelectDMC editor
        {
            "field": "warehouse_id",
            "headerName": "From Warehouse",
            "initialWidth": 200,
            "maxWidth": 200,
            "editable": True,
            "cellEditorPopup": True,
            "singleClickEdit": True,
            "cellEditor": {"function": "SelectDMC"},
            "cellEditorParams": {
                "options": warehouse_options,
                "placeholder": "Select a warehouse",
                "maxDropdownHeight": 280,
                "creatable": False,
                "clearable": True,
            },
            "valueFormatter": {"function": "getDisplayLabel(params)"},
        },
        # Other standard columns
        {
            "field": "id",
            "headerName": "ID",
            "filter": True,
        },
        {"field": "product", "headerName": "Product", "filter": True, "editable": True},
        {
            "field": "quantity",
            "headerName": "Quantity",
            "filter": True,
            "editable": True,
        },
        {"field": "price", "headerName": "Price ($)", "filter": True, "editable": True},
    ]

# Define app layout
dash_app.layout = dmc.MantineProvider(
    dbc.Container(
        class_name="m-3",
        children=dbc.Row(
            dbc.Col(
                dbc.Card(
                    [
                        dbc.CardHeader(
                            html.H1("AG Grid Demo with Dash and Flask"),
                        ),
                        dbc.CardBody(
                            [
                                dbc.Row(
                                    dbc.Col(
                                        dbc.Alert(
                                            "Click the button below to update the data and toggle the price column",
                                            color="info",
                                        ),
                                    ),
                                    class_name="m-3",
                                ),
                                dbc.Row(
                                    dbc.Col(
                                        dbc.Button(
                                            "Update Data & Toggle Column",
                                            id="update-button",
                                            color="success",
                                            className="ms-auto",
                                        ),
                                    ),
                                    class_name="m-3",
                                ),
                                dbc.Row(
                                    dbc.Col(
                                        get_ag_grid(
                                            id="grid",
                                            rowData=initial_row_data,
                                            columnDefs=get_column_defs(),
                                            dashGridOptions={
                                                "rowSelection": "multiple",
                                                "suppressRowClickSelection": True,
                                                "animateRows": True,
                                                "popupParent": {
                                                    "function": "setBody()"
                                                },
                                            },
                                            className="ag-theme-alpine",
                                            style={
                                                "height": "400px",
                                                "width": "100%",
                                            },
                                        ),
                                    ),
                                    class_name="m-3",
                                ),
                                dbc.Row(
                                    dbc.Col(
                                        class_name="m-3",
                                        children=[
                                            html.H3("Current Row Data:"),
                                            html.Pre(
                                                id="row-data-display",
                                                style={
                                                    "backgroundColor": "#f5f5f5",
                                                    "padding": "10px",
                                                    "border": "1px solid #ddd",
                                                    "borderRadius": "5px",
                                                    "overflowX": "auto",
                                                    "whiteSpace": "pre-wrap",
                                                },
                                            ),
                                        ],
                                    ),
                                    class_name="m-3",
                                ),
                                # # Store components for state management
                                # dcc.Store(
                                # id="row-data-store",
                                # data=initial_row_data,
                                # ),
                                # dcc.Store(
                                # id="column-defs-store",
                                # data=get_column_defs(),
                                # ),
                            ]
                        ),
                    ]
                ),
            )
        ),
    )
)

@callback(Output("row-data-display", "children"), Input("grid", "rowData"))
def display_row_data(row_data):
    """
    This callback formats and displays the current rowData as JSON
    in the dedicated HTML div element.
    """
    if row_data is None:
        return "No data available"
    return json.dumps(row_data, indent=2)

@callback(
    Output("grid", "columnDefs"),
    Input("update-button", "n_clicks"),
    State("grid", "columnDefs"),
    # State("column-defs-store", "data"),
    prevent_initial_call=True,
)
def update_column_defs(n_clicks, current_column_defs):
    """
    This callback toggles the visibility of the price column
    when the update button is clicked.
    """
    if n_clicks is None:
        raise PreventUpdate

    # updated_column_defs = get_column_defs()
    updated_column_defs = current_column_defs.copy()

    # Find the price column
    for i, col in enumerate(updated_column_defs):
        if col["field"] == "price":
            # Toggle hide property (or set it if it doesn't exist)
            updated_column_defs[i]["hide"] = not col.get("hide", False)
            break

    return updated_column_defs

@callback(
    Output("grid", "rowData"),
    # Output("row-data-store", "data"),
    Input("update-button", "n_clicks"),
    # State("row-data-store", "data"),
    State("grid", "rowData"),
    prevent_initial_call=True,
)
def update_row_data(n_clicks, current_data):
    """
    This callback generates new row data when the update button is clicked.
    It alternates between adding new products and updating quantities.
    """
    if n_clicks is None:
        return current_data

    # Determine what type of update to perform based on click count
    if n_clicks % 2 == 1: # Odd clicks: Add new products
        new_products = [
            # {
            # "delete": "",
            # "id": len(current_data) + 1,
            # "product": "Headphones",
            # "quantity": 35,
            # "warehouse_id": "wh1",
            # "price": 149.99,
            # },
            # {
            # "delete": "",
            # "id": len(current_data) + 2,
            # "product": "Printer",
            # "quantity": 10,
            # "warehouse_id": "wh3",
            # "price": 299.99,
            # },
        ]
        return current_data + new_products
    else: # Even clicks: Update quantities
        updated_data = current_data.copy()
        for item in updated_data:
            # Increase quantity by a random amount (10-50)
            import random

            item["quantity"] += random.randint(10, 50)
        return updated_data

# @callback(Output("grid", "rowData"), Input("row-data-store", "data"))
# def update_grid_data(row_data):
# """
# This callback updates the grid with the current rowData from the store.
# This is necessary because we need to update the grid when the store changes.
# """
# return row_data

if __name__ == " __main__":
    app.run(debug=True, host="0.0.0.0", port=4999)

```

---

<div class="post-metadata">

**Author:** ![AnnMarieW](https://avatars.discourse-cdn.com/v4/letter/a/ec9cab/32.png) [@AnnMarieW](https://community.plotly.com/u/AnnMarieW)\
**Post date:** [March 15, 2025, 4:53pm UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700/8 "2025-03-15T16:53:35Z")

</div>

@seanrez  
Use the `virtualRowData` prop rather than `rowData` to track the changes.

---

<div class="post-metadata">

**Author:** ![jinnyzor](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/jinnyzor/32/21194_2.png) [@jinnyzor](https://community.plotly.com/u/jinnyzor)\
**Post date:** [March 17, 2025, 12:15pm UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700/9 "2025-03-17T12:15:37Z")

</div>

What happens if you instead use `rowTransaction`? And use the clientside set\_props?

---

<div class="post-metadata">

**Author:** ![ctdunc](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/ctdunc/32/30458_2.png) [@ctdunc](https://community.plotly.com/u/ctdunc)\
**Post date:** [March 18, 2025, 6:12pm UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700/10 "2025-03-18T18:12:54Z")

</div>

I’ve found that I am able to delete rows successfully only when using `applyTransactionAsync`.

The `rowTransaction` prop and `applyTransaction` both fail to update the state correctly when using row ids (my specific use case is with tree data).

It needs to be used in a clientside callback. e.g.

```auto

clientside_callback(
      """
      (_, filter_rows, filterTableId) => {
        const api = dash_ag_grid.getApi(filterTableId);
        api.applyTransactionAsync({
          add: [...],
        });
        return dash_clientside.no_update;
      };
      """,
      Output(ids.filter_table(MATCH), "id", allow_duplicate=True),
      Input(ids.add_filter(MATCH), "n_clicks"),
      State(ids.filter_table(MATCH), "rowData"),
      State(ids.filter_table(MATCH), "id"),
      prevent_initial_call=True,
)

```

---

<div class="post-metadata">

**Author:** ![jinnyzor](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/jinnyzor/32/21194_2.png) [@jinnyzor](https://community.plotly.com/u/jinnyzor)\
**Post date:** [March 19, 2025, 1:42pm UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700/11 "2025-03-19T13:42:41Z")

</div>

`rowTransaction` is set to apply the same mechanism as `applyTransactionAsync`, this should be defaulting to use `applyTransactionAsync` by default and you need to pass `async: False` in order to not apply this.

---

<div class="post-metadata">

**Author:** ![MileinaCelesteria](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/mileinacelesteria/32/34340_2.png) [@MileinaCelesteria](https://community.plotly.com/u/MileinaCelesteria)\
**Post date:** [July 6, 2026, 6:31pm UTC](https://community.plotly.com/t/deleting-rows-in-dash-ag-grid/78700/12 "2026-07-06T18:31:54Z")

</div>

```auto
var dagcomponentfuncs = window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {};

dagcomponentfuncs.DeleteButton = function (props) {
    function onClick() {
        const rowId = props.node.data.id;

        // send id to Dash
        props.setData({
            id: rowId
        });

        // delete row from grid
        props.api.applyTransaction({
            remove: [props.node.data]
        });
    }
    return React.createElement('button', {onClick}, "X");
};

```

bit refinement, for the js… to send data back to callback so it can properly interact with the data

usage. put this in your callback  
`Input(“Agrid_id”, “cellRendererData”),` ####{value:{id:5}…}
