Plotly Dash: How to reset the "n_clicks" attribute of a dash-html.button?

Publish date: 2024-05-18

I have a basic datatable in plotly/dash. My goal is to upload (or print for the sake of the example...) after I press the upload-button.

The issue is, that I can't figure out how to get the n_clicks attribute of the button back to zero. So what happens is that after I clicked the button for the first time it prints continuously whenever something changes (row added or number changed/added), but what I want is for it to print only once whenever I click the button.

This is the code:

import dash from dash.dependencies import Input, Output, State import dash_table import dash_daq as daq import dash_core_components as dcc import dash_html_components as html import pandas as pd external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] df = pd.read_csv('.../dummy_csv.csv') app = dash.Dash(__name__) app.layout = html.Div([ html.Div(id='my-div'), dash_table.DataTable( id='adding-rows-table', style_data={ 'whiteSpace': 'normal', 'height': 'auto' }, style_table={ 'maxHeight': '800px' , 'overflowY': 'scroll' }, columns=[ {'name': i, 'id': i} for i in df.columns ], data=df.to_dict('records'), editable=True, row_deletable=True ), html.Button('+ Row', id='editing-rows-button', n_clicks=0), html.Button('Update', id='btn-nclicks-1', n_clicks=0 ), ]) @app.callback( Output(component_id='my-div', component_property='children'), [Input('btn-nclicks-1', 'n_clicks'), Input('adding-rows-table', 'data')] ) def update_output_div(n_clicks, data): if n_clicks > 0: print(data) # n_clicks = 0 # return n_clicks else: print("else loop") @app.callback( Output('adding-rows-table', 'data'), [Input('editing-rows-button', 'n_clicks')], [State('adding-rows-table', 'data'), State('adding-rows-table', 'columns')]) def add_row(n_clicks, rows, columns): if n_clicks > 0: rows.append({c['id']: '' for c in columns}) return rows if __name__ == '__main__': app.run_server(debug=True) 

This the CSV:

a,b,c,d 1,1,5,1 2,2,5,1 2,33,6,2 3,4,6,2 

And this is the "faulty" output.

3 Answers

You could use the dash.callback_context property to trigger the callback only when the number of clicks has changed rather than after the first click. See the section on "Determining which Button Changed with callback_context" in the Dash documentation. The following is an example of how you could update your callback.

@app.callback(Output(component_id='my-div', component_property='children'), [Input('btn-nclicks-1', 'n_clicks'), Input('adding-rows-table', 'data')]) def update_output_div(n_clicks, data): changed_id = [p['prop_id'] for p in dash.callback_context.triggered][0] if 'btn-nclicks-1' in changed_id: print(data) # n_clicks = 0 # return n_clicks else: print("else loop") 
2

You can reset the n_clicks=None attribute, and it won't trigger an infinite loop when used in combination with a PreventUpdate null condition that you are probably already using.

@callback( Output('some_btn', 'n_clicks'), Input('some_btn', 'n_clicks') ) def upon_click(n_clicks): if (n_clicks is None): raise PreventUpdate print(n_clicks)#1 return None 

It's a bit more explicit and faster than callback_context 🤷‍♂️ For example, my button's id contained a complex "pattern-matching callback" so I didn't want to parse the callback context for that id

An option that worked for me is: n_clicks = 0 in the button declaration. create list and add click data, then check if next index is greater than current index.

An approximate example with your code would be:

list_clicks = [] @app.callback( Output('adding-rows-table', 'data'), [Input('editing-rows-button', 'n_clicks')], [State('adding-rows-table', 'data'), State('adding-rows-table', 'columns')]) def add_row(n_clicks, rows, columns): list_clicks.append(n_clicks) for index in range(len(list_clicks)-1): if list_clicks[index + 1] > list_clicks[index]: list_clicks.clear() rows.append({c['id']: '' for c in columns}) return rows 

clicking on the button n_clicks is increased by 1. What the code block does is compare if the current index is greater than the previous one. If it is greater, it means that the button was clicked.

list_clicks.clear() it is to clean the list and not iterate over the same passed values.

maybe you could adapt it better to your code!

ncG1vNJzZmirpJawrLvVnqmfpJ%2Bse6S7zGiorp2jqbawutJobWtuZ2Z%2Fc4KOqaOorJyueqWt0qFkoaenYsGwedGeqp6sXam1pnnNZpqloZOgwG6t062popqlqbJuu8VmmGackai1brTTpqNmmqWpwbC6