Add rows to datatable via clientside_callback not expanding table

When I substitute the simple callback in the example for adding rows to a datatable for a clientside callback, the table does not expand and display the new row. If I delete a row with the row_deletable argument the table will update showing all of the added rows. However, when I run it through a normal callback everything works as shown in the example. I’m guessing there’s something else that must be done with the component via clientside to get it to update?

### THIS DOES NOT WORK 
        clientside_callback(
            """
            function(n1, data, columns) {
                if (n1) {
                    const newRow = columns.reduce((obj, col) => {
                        obj[col.id] = null;
                        return obj;
                    }, {});
                    data.push(newRow);
                }
                return data
            }
            """,
            Output({'type': f'{self.instance_name}_debt_scheduler'}, 'data'),
            Input({'type': f'{self.instance_name}_debt_scheduler_add_row'}, 'n_clicks'),
            State({'type': f'{self.instance_name}_debt_scheduler'}, 'data'),
            State({'type': f'{self.instance_name}_debt_scheduler'}, 'columns'),
            pervent_initial_call=True
        )

#### THIS WORKS
        @callback(
            Output({'type': f'{self.instance_name}_debt_scheduler'}, 'data'),
            Input({'type': f'{self.instance_name}_debt_scheduler_add_row'}, 'n_clicks'),
            State({'type': f'{self.instance_name}_debt_scheduler'}, 'data'),
            State({'type': f'{self.instance_name}_debt_scheduler'}, 'columns'),
            pervent_initial_call=True
        )
        def add_row(n_clicks, rows, columns):
            if n_clicks:
                rows.append({c['id']: '' for c in columns})
            return rows

Hi @mworth

The reason it’s not working in the clientside callback is that it’s updating the row data in place, which doesn’t trigger the app to re-render the table.

Try running this example:



from dash import Dash, dash_table, dcc, html, Input, Output, State, clientside_callback

app = Dash(__name__)

app.layout = html.Div([
    html.Button('Add Row', id='editing-rows-button', n_clicks=0),

    dash_table.DataTable(
        id='adding-rows-table',
        columns=[{
            'name': 'Column {}'.format(i),
            'id': 'column-{}'.format(i),
            'deletable': True,
            'renamable': True
        } for i in range(1, 5)],
        data=[
            {'column-{}'.format(i): (j + (i-1)*5) for i in range(1, 5)}
            for j in range(5)
        ],
        editable=True,
        row_deletable=True
    ),
])


clientside_callback(
    """
     function(n_clicks, rows, columns) {    
        if (n_clicks > 0) {
            let newRow = {};
            columns.forEach(column => {
                newRow[column.id] = '';
            });               
            // Create a deep copy of the rows array - ensures re-render
            let newRows = JSON.parse(JSON.stringify(rows));
            newRows.push(newRow)
            return newRows
        }        
        return dash_clientside.no_update
    }
    """,
    Output('adding-rows-table', 'data'),
    Input('editing-rows-button', 'n_clicks'),
    State('adding-rows-table', 'data'),
    State('adding-rows-table', 'columns')
)



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

Ah, makes sense! Thanks so much!