Dynamically changing 'disable' state of dbc.Tab

Hi everyone,

In my code i was able to change the ‘disable’ state of dbc.Tab based on the availability of calculated results. This doesn’t work anymore and i’m wondering if it’s related to recently updating to Dash 3.0.1, combined with dash-bootstrap-components 2.0.0 ? Below is a simplified example:

import dash
import dash_bootstrap_components as dbc
from dash import dcc, html
from dash.dependencies import Input, Output

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div([
    html.Button('Enable Tabs', id='button', n_clicks=0),
    dcc.Store(id='store-data', data=None),

    dbc.Tabs(
        [
            dbc.Tab(label='Tab A', tab_id='tab-a', id='tab-a', disabled=True),
            dbc.Tab(label='Tab B', tab_id='tab-b', id='tab-b', disabled=True),
        ],
        id='tabs',
        active_tab='tab-a'
    )
])

@app.callback(
    Output('store-data', 'data'),
    Input('button', 'n_clicks')
)
def update_store_data(clicks):
    if clicks > 0:
        return {"data": "available"}
    return None

@app.callback(
    [
        Output('tab-a', 'disabled'),
        Output('tab-b', 'disabled')
    ],
    Input('store-data', 'data')
)
def toggle_tabs(store_data):
    if store_data is not None and "data" in store_data:
        return False, False
    return True, True

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

Variable ‘store-data’ is populated after a button is clicked, and the change in ‘store-data’ should enable the tabs.

@AnnMarieW is this a known compatibility issue between Dash 3.0 and DBC?

@adamschroeder
It looks like it might be an issue with dash 3. This version doesn’t use dbc and it works with dash 2.18.2 but not with dash 3.0.1


import dash
from dash import dcc, html, Input, Output

app = dash.Dash()

app.layout = html.Div([
    html.Button('Enable Tabs', id='button', n_clicks=0),
    dcc.Store(id='store-data', data=None),

    dcc.Tabs(
        [
            dcc.Tab(label='Tab A', value='tab-a', id='tab-a', disabled=True),
            dcc.Tab(label='Tab B', value='tab-b', id='tab-b', disabled=True),
        ],
        id='tabs',
        value='tab-a'
    )
])

@app.callback(
    Output('store-data', 'data'),
    Input('button', 'n_clicks')
)
def update_store_data(clicks):
    if clicks > 0:
        return {"data": "available"}
    return None

@app.callback(
    [
        Output('tab-a', 'disabled'),
        Output('tab-b', 'disabled')
    ],
    Input('store-data', 'data')
)
def toggle_tabs(store_data):
    if store_data is not None and "data" in store_data:
        return False, False
    return True, True

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



1 Like

Hello @Dixit,

After some digging, it looks like the Tabs component treats the tab in a strange manner. Here is the way to render the adjustments to the disabled going forward:

import dash
from dash import dcc, html, Input, Output, Patch

app = dash.Dash()

app.layout = html.Div([
    html.Button('Enable Tabs', id='button', n_clicks=0),
    dcc.Store(id='store-data', data=None),

    dcc.Tabs(
        [
            dcc.Tab(label='Tab A', value='tab-a', id='tab-a', disabled=True),
            dcc.Tab(label='Tab B', value='tab-b', id='tab-b', disabled=True),
        ],
        id='tabs',
        value='tab-a'
    )
])

@app.callback(
    Output('store-data', 'data'),
    Input('button', 'n_clicks')
)
def update_store_data(clicks):
    if clicks > 0:
        return {"data": "available"}
    return None

@app.callback(
    Output('tabs', 'children'),
    Input('store-data', 'data')
)
def toggle_tabs(store_data):
    children = Patch()
    if store_data is not None and "data" in store_data:
        children[0]['props']['disabled'] = False
        children[1]['props']['disabled'] = False
    return children

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

This worked in Dash 2 because the whole app is being rerendered with each prop change, Dash 3 renders from the target component and down. Also, since the Tab is not registered with Dash, I fear that anything under it is also missing… — thankfully I was wrong, the inputs still trigger, so seems like its just a Tab issue. :slight_smile:


This is, of course, not the desired way to perform this, but will take a refactor of how the tabs work. :smiley:

1 Like

Nice workaround @jinnyzor

I opened an issue for it here:

Thank you for the workaround code, @jinnyzor
And thank you @AnnMarieW for opening an issue.

Thank you all. The workaround code works with DBC as well.

@jinnyzor workaround also worked for my dbc.tab components inside the running callback argument.

running=[
    (Output('tab-group', "children[0]['props']['disabled']"), True, False),
    (Output('tab-2', "disabled"), True, False),
    (Output('tab-3', "disabled"), True, False),
]

I believe that dbc has an update with this as well already. :slight_smile: It might be an rc though.