AG Grid: Delete row AND update rowData?

Hi!

I have 3 buttons:

  • add row
  • delete row
  • save (to SQL)

First two (adding and deleting rows) are in the same callback.
Here is the code for that callback:

@callback(
        Output("sql-table", "rowData"),                      #<======== not updating?
        Output("sql-table", "deleteSelectedRows"),
        Output("selected-data", "rowData"),
        Input("delete-row-button", "n_clicks"),
        Input("add-row-button", "n_clicks"),
        State("sql-table", "rowData"),
        State("sql-table", "selectedRows"),
        prevent_initial_call = True
)

def update_dash_table(n_dlt, n_add, data, selection):
    if ctx.triggered_id == "add-row-button":
        df=pd.DataFrame(data)
        new_row = {
            "test": [x],
        }
        df_new_row = pd.DataFrame(new_row)
        if df.empty == False :
            df_new_row.columns=df.columns
            updated_table = pd.concat([df, df_new_row], ignore_index=True)
        else:
            updated_table = df_new_row
        return updated_table.to_dict("records"), no_update, no_update

    elif ctx.triggered_id == "delete-row-button":
        if selection is None:
            return no_update, no_update, no_update
        return no_update, selection, selection                #<==== problem here?

Data itself is coming from another callback which in its turn is pulling data from SQL.
When I click “add row” and then “save”, row is added and data (incl. the new row) is saved to SQL. It works.
When I select row, click “delete row” and then “save”, selected row is deleted and data (excl. the deleted row) is saved to SQL. It works.
Problem arises when I combine. Example: when I add two rows by clicking “add row” twice and then, for example, delete one of these rows (by selecting and clicking “delete row”) and ONLY THEN click “save”, I hope that the newly added row that I chose not to delete will still remain and will be saved. But it doesn’t work. None of rows is saved.

I believe, it is due to no_update in the line return no_update, selection, selection.

I think what happens is: no_update in the line return no_update, selection, selection doesn’t allow Output("sql-table", "rowData") to be updated.

Is there a way to force an update to Output("sql-table", "rowData") after deleting a one of rows (but still keeping the other row) ?
Is there any alternative to no_update ?

Thanks for any replies and suggestions in advance.

Hello @mrel,

Try using rowTransaction instead.

2 Likes

Hi @jinnyzor ,

Tried following the documentation .

Adjusted the code:

@callback(
        Output("sql-table", "rowTransaction"),
        Output("sql-table", "deleteSelectedRows"),
        Output("selected-data", "rowData"),
        Input("delete-row-button", "n_clicks"),
        Input("add-row-button", "n_clicks"),
        State("sql-table", "rowData"),
        State("sql-table", "selectedRows"),
        prevent_initial_call = True
)

def update_dash_table(n_dlt, n_add, data, selection):
    if ctx.triggered_id == "add-row-button":
        df=pd.DataFrame(data)
        new_row = {
            "test": [x],
        }
        df_new_row = pd.DataFrame(new_row)
        return {"add": df_new_row}, no_update, no_update
        # return {"add": df_new_row.to_json(date_format='iso', orient='split')}, no_update, no_update

    elif ctx.triggered_id == "delete-row-button":
        if selection is None:
            return no_update, no_update, no_update
        return {"remove": selection}, selection, selection

Deleting the row works.

Now there is a problem with adding a row.

If I use return {"add": df_new_row}, no_update, no_update then I am getting an error:

File "C:\Users\PC Desk\AppData\Local\Programs\Python\Python310\lib\site-packages\dash\_validate.py", line 238, in _raise_invalid    raise exceptions.InvalidCallbackReturnValue(
dash.exceptions.InvalidCallbackReturnValue: The callback for `[<Output `sql-table.rowTransaction`>, <Output `sql-table.deleteSelectedRows`>, <Output `selected-data.rowData`>]`
                returned a value having type `dict`
                which is not JSON serializable.


The value in question is either the only value returned,
or is in the top level of the returned list,

                and has string representation
                `{'add':    entry  year      month  day category1 category2 name comment quantity price delivery amount link      
type concatenate
0     27  2023  September   16                                                                       expenses           x}`       

                In general, Dash properties can only be
                dash components, strings, dictionaries, numbers, None,
                or lists of those.

If I am using return {"add": df_new_row.to_json(date_format='iso', orient='split')}, no_update, no_update to turn the df to json, error is disappearing, but at the same time row is not getting added. Nothing happens. No error, no added row.

Any idea why it could be happening?

I normally just return a list from df_new_row.to_dict('records')

1 Like

Ok, that worked. Error disappeard.

Also it allowed me to find a bug in my “save” button. Now everything works as intended.

Thank you!

1 Like

A little late but I was able to setup another way to achieve this:

JS file:

dagcomponentfuncs.DeleteButton = function (props) {
  function onClick() {
    // do whatever you want this (JavaScript), not needed for the callback
  }
  return React.createElement(
    "button",
    {
      onClick,
    },
    "X"
  );
};

You need to link the React component with the delete column definition by doing this in your Python file:

 column_defs.append(
        {
            "field": "Delete",
            "cellRenderer": "DeleteButton",
        }
    )

and the callback to


@callback(
    Output("table-id", "rowData"),
    Input("table-id", "cellClicked"),
    Input("table-id", "rowData"),
    prevent_initial_call=True,
)
def delete_row(cell_clicked, data):
   # cell_clicked contains the information of the event clicked, here we need to filter by the colId
   # [Cell Selection | Dash for Python Documentation | Plotly](https://dash.plotly.com/dash-ag-grid/cell-selection#cell-simple-click-example)
   if cell_clicked["colId"] == "Delete":
      data.pop(cell_clicked["rowIndex"])
   return data

This is a basic example, in my case I had to add more safe checks (check rowIndex > -1, etc) or sometimes its being triggered twice so adding a filter by trigger_id would help:
Advanced Callbacks | Dash for Python Documentation | Plotly

1 Like

Hello @epacheco,

Welcome to the community!

It is also possible to utilize this by using the Patch method of dash, to where instead of passing all the data back, you only pass the one command to delete.

Using the rowTransaction calls a native function inside of AG Grid, which is why it is also my recommended way of making adjustments.

Another thing to point out, you can use a callback based upon the cellRendererData which is available from the custom components. :slight_smile:

This callback definition should really be something like this:

@callback(
    Output("table-id", "rowData"),
    Input("table-id", "cellClicked"),
    State("table-id", "rowData"),
    prevent_initial_call=True,
)

This way, the rowData doesnt trigger and then send back data, but instead is a value that gets referenced. :slight_smile:

Hi! thanks for your reply,

I’ll try again with your suggestions, I had some issues trying to use rowTransaction and the client callback, for some reason it was not working as expected (not triggering the callback after hit the delete button), but might be something on my project.

1 Like

If you need more help, make a new topic and post some code.