dcc.Tabs: Filling tabs with dynamic content. How to organize the callbacks?

Hi everyone,

I’d like to make use of the new dcc.Tabs to switch between multiple levels of detail in a visualization. To do this I’d like to fill the contents of the different tabs dynamically, but I haven’t figured out how to do this yet as the elements within the tab component (in my example below the dcc.Graph and the dt.DataTable) are not within the app,layout and therefore cause an error. Is there a way to solve this?

An example of what I’d like to do:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dt

app = dash.Dash()

app.layout = html.Div([
        html.Div([dcc.Dropdown(
                 id='dropdown',
                 options=[{'label': 'Option 1', 'value': 1},
                          {'label': 'Option 2', 'value': 2}],
                 value='1',
                 )]),
        dcc.Tabs(tabs=[{'label': 'Tab 1', 'value': 1},
                       {'label': 'Tab 2', 'value': 2}],
                 value=3,
                 id='tabs',
                 ),
        html.Div(id='tab-output')
    ])


@app.callback(Output('tab-output', 'children'), [Input('tabs', 'value')])
def display_content(value):
    if value == 1:
        return html.Div([dcc.Graph(id='graph')]) #etc. etc.
    elif value == 2:
        return html.Div([dt.DataTable(id='table')]) #etc. etc.


@app.callback(Output('graph', 'figure'), [Input('dropdown', 'value')])
def update_graph(value):
    return something
    # callback that does something to graph


@app.callback(Output('datatable', 'rows'), [Input('dropdown', 'value')])
def update_graph(value):
    return something
    # callback that does something to table


if __name__ == '__main__':
    app.run_server(debug=True)
2 Likes

Hey @geertjes, thanks for reaching out!

The error message that is usually thrown in this case looks like this:

dash.exceptions.NonExistantIdException:
Attempting to assign a callback to the
component with the id "graph" but no
components with id "graph" exist in the
app's layout.


Here is a list of IDs in layout:
['dropdown', 'tabs', 'tab-output']


If you are assigning callbacks to components
that are generated by other callbacks
(and therefore not in the initial layout), then
you can suppress this exception by setting
`app.config['suppress_callback_exceptions']=True`.

Following the instructions in the error message, we can remove this exception by setting

app.config['suppress_callback_exceptions'] = True

Now, why does this work?
When we assign callbacks, Dash traverses the existing app.layout to collect all of the component ids and checks that the ids that you are using the callbacks have actually been defined. So, if you make a typo, Dash will fail early and fast and tell you which ID you supplied and which ID was available:

app.layout = html.Div([
    html.Dropdown(id='my-dropdown'),
    html.Graph(id='my-graph'),
])

@app.callback(Output('my-graph-typo', 'figure'), [Input('my-dropdown', 'value')])
def update_graph(value):
    return ...
dash.exceptions.NonExistantIdException:
Attempting to assign a callback to the
component with the id "my-graph-typo" but no
components with id "my-graph-typo" exist in the
app's layout.


Here is a list of IDs in layout:
['my-graph', 'my-dropdown']


If you are assigning callbacks to components
that are generated by other callbacks
(and therefore not in the initial layout), then
you can suppress this exception by setting
`app.config['suppress_callback_exceptions']=True`.

When we generate dynamic components from callbacks (as in your case), Dash has no way of knowing which IDs you are generating. After all, the IDs could be totally dynamic! Dash assumes that you are making a typo by assigning a callback to an ID that doesn’t exist at app start but allows you to manually correct this assumption by setting app.config['suppress_callback_exceptions']=True. This forces you to acknowledge that Dash will not protect you against ID exceptions.


In your example, I notice that you are also rendering DataTable dynamically. This requires some special care, see Display tables in Dash - #40 by chriddyp for a discussion and solution.


Let me know if this makes sense! If you have any suggestions on how to make this exception easier to understand, I’m all ears :slight_smile:

4 Likes

Thank you so much @chriddyp for making this platform! I was wondering roughly when do you think the “Tabs” component will be released in full? Thanks!

I want to add that one way of protecting against typos is to define your IDs as variables, then a static linting check will at least make sure your variables exist and are properly typed.

1 Like

Thanks - this solution was key to making my app work! It’s unlikely that I would want to make a tabbed app without dynamic content, so this will be a part of pretty much any app I build going forward (just as dcc.tabs itself will be a staple). Much appreciated.

Hi Chriddy,

I have a similar issue. When I have a dynamic ID and used it in the following snippet as the input, it won’t even pass the sytax check.

@app.callback(
    Output(component_id='home-plot-1', component_property='children'),
    [Input(component_id='tabs', component_property='value')]
)
def sensor_data_plot_callback(tabs):
    if tabs != 'HOME-tab':
        return None

    home_tab = html.Div([
                    dcc.Tabs(id="tabs-v", value='calculated-tab', vertical=True, children=[
                        dcc.Tab(label='Calculated', value='calculated-tab', style=tab_style, selected_style=tab_selected_style),
                        dcc.Tab(label='Sensor Data', value='sensor-data-tab', style=tab_style, selected_style=tab_selected_style)
                    ], style=tabs_styles),
                    html.Div(id='tabs-content-inline')
                ])

    return home_tab

@app.callback(
    Output(component_id='home-calc-1', component_property='children'),
    [Input(component_id='tabs-v', component_property='value')]
)
def dummy(tabs-v):
        return html.H1('Home Calc Tab Content')
def dummy(tabs-v):
              ^

SyntaxError: invalid syntax

I had the same problem - all callback_exceptions_suppressed as recommended .

app.config.suppress_callback_exceptions = True
app.server.config.suppress_callback_exceptions = True
app.config[‘suppress_callback_exceptions’] = True

(as it isn’t really clear which one of these should be used - they all appear in the docs!)

Page was so slow to load and looking at Web dev tools react was echoing the error. When I duplicated the ALL the id’s in every layout and hid them - page was back to lightening quick.

It appears the suppress callback wasn’t blocking effective in making the code run quickly…

Somehow this is not suppressing the debug error messages.

In more details:

Any ideas why I am still getting errors? :slight_smile:

The error you see is due to an invalid variable name, you cannot use “-” in Python,

1 Like

Thanks for your answer @Emil, but as my inputs are being generated depending on some options selected by the user, it is true that the object doesn’t exist at the starting time.

Example:

input_callback=[dash.dependencies.Input(f"input_component_{i}", 'n_clicks') for i in range(len(user_option))]
output_callback=[dash.dependencies.Output("output_component", "disabled")]
@app.callback(
    output_callback,
    input_callback,
)
def fcn(*args):
   return [True]

Couldn’t this ID checking happen in step with the callbacks graph; in phases?

As soon as I saw “suppress” I thought it was just about hiding warnings as opposed to changing how the framework itself works.

In a lot of data science packages “suppress” is just about hiding annoying warnings. So I didn’t think to actually change this setting. I kept looking in my program for bugs.