Black Lives Matter. Please consider donating to Black Girls Code today.

Dynamic tabs triggers callback recursively

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.