I was looking to have a main tab, which would have a button which when pressed, would add a new tab using the ‘n_click’ prop… after pressing the button once, the callback keeps triggering, even though the n_click prop has not changed. Attached is the toy example which maybe someone can explain what is triggering it recursively.
import dash
from dash.dependencies import Output, Input, State
import dash_html_components as html
import dash_core_components as dcc
from dash.exceptions import PreventUpdate
app = dash.Dash(__name__)
app.scripts.config.serve_locally = True
app.config.suppress_callback_exceptions = True
app.layout = html.Div([
dcc.Tabs(id="tabs", children=[
dcc.Tab(label="main", value="main",children=[
html.Div(children=[html.H1("Main"),
html.Button('click me', id='btn')])
])
])
])
@app.callback(Output('tabs', 'children'),
[Input('btn', 'n_clicks')],
[State('tabs', 'children')])
def f1(n_clicks, data):
if n_clicks is None:
raise PreventUpdate
print("f1 called {}".format(n_clicks))
data+= [dcc.Tab(label=str(n_clicks), value=str(n_clicks))]
return data
if __name__ == '__main__':
app.run_server(debug=True)
I assume because from the HTML documents point of view you are destroying the old element that contains the id “btn” and creating a new one. Therefore it is triggered because for a moment it didn’t exist and now it exists again.
Modifying a parents structure from a child is a little tricky in Dash. Are you sure you just can’t create the button outside the tabs?
e.g.:
import dash
from dash.dependencies import Output, Input, State
import dash_html_components as html
import dash_core_components as dcc
from dash.exceptions import PreventUpdate
app = dash.Dash(__name__)
app.scripts.config.serve_locally = True
app.layout = html.Div([
html.Div(children=[html.H1("Main"), html.Button('click me', id='btn')]),
dcc.Tabs(id="tabs",
value='main',
children=[
dcc.Tab(label="main",
value="main",
children=['main'])
])
])
@app.callback(Output('tabs', 'children'),
[Input('btn', 'n_clicks')],
[State('tabs', 'children')])
def f1(n_clicks, data):
if n_clicks is None:
raise PreventUpdate
print("f1 called {}".format(n_clicks))
data += [dcc.Tab(label=str(n_clicks), value=str(n_clicks))]
return data
if __name__ == '__main__':
app.run_server(debug=True)
I thought that, but the actual button object is not actually destroyed, as it retains the n_clicks value, so I thought it might be something different… When I pull the button out and put it in a Div above the Tab structure, it definitely works as intended… now I need to check and make sure that works in the real application… thanks for the suggestion.
It keeps the n_clicks value because when you capture the children from State('tabs', 'children') it has n_clicks value associated with it, you then add the tabs children and resend to tabs children preserving the n_clicks.