Is there a way to synchronize dropdowns across tabs

I have an app with dcc.Tabs, using method two from this tutorial: dcc.Tabs | Dash for Python Documentation | Plotly

Two of the tabs have dropdowns that are related to one another. Is there any way to get them synchronized so that when one changes it updates the value of the other?

I’ve tried something below, which worked for one dropdown, but when I created the corresponding version for the other one, the app returned an ‘Error loading dependencies’

# Synchronize with temp employment dropdown
app.callback(Output('tab-one-dropdown', 'value'),
               [Input('tab-two-dropdown', 'value')])
 def synchronize_dropdown(value):
     return value

2 Likes

Hey,

I’m trying to do something similar, I don’t have a clear explanation or a best practice advice, but so far I’ve succeed to pass data between tabs using the dcc.store component. Here is an example:

import dash

import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Output, Input, State
from dash.exceptions import PreventUpdate


external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.config['suppress_callback_exceptions'] = True

app.layout = html.Div([

    dcc.Store(id='store_1', storage_type='session'),

    html.Div([
        dcc.Tabs(id="tabs", value=0, children=[
            dcc.Tab(label='app1', value='app1'),
            dcc.Tab(label='app2', value='app2'),
        ]),

    ]),

    html.Br(),

    html.Div(id='page-content'),

])


app1_layout = html.Div([

     html.Div(

        dcc.Checklist(
             id='erase_stored_data',
             options=[
                 {'label': 'n-clicks', 'value': 0},
            ],
            values=[],
            labelStyle={'display': 'inline-block'}
        ),
        id='erase_checklist_container',

    ),

    html.Button('Hey yo !', id='button_1'),

    html.Div(id='display_clicks')

])

app2_layout = html.Div([

    html.Div(id='display_state_app2')

])


# DISPATCH LAYOUT
@app.callback(Output('page-content', 'children'), [Input('tabs', 'value')])
def display_page(tab_value):
    if tab_value == 'app1':
        return app1_layout
    elif tab_value == 'app2':
        return app2_layout
    else:
        return html.Div('oOOouups ; I ; did it again...')


# STORE N-CLICKS in dcc.Store 1
@app.callback(Output('store_1', 'data'),
              [Input('button_1', 'n_clicks')],
              [State('store_1', 'data')])
def upload_data(n_clicks, data):
    if n_clicks is None:
        raise PreventUpdate

    data = data or {'clicks': 0}

    data['clicks'] = data['clicks'] + 1

    return data


# DISPLAY N-CLICKS and dcc.Store TIMESTAMPS
@app.callback(Output('display_clicks', 'children'),
              [Input('store_1', 'modified_timestamp')],
              [State('store_1', 'data')])
def display_clicks(ts, state_data):
    if ts is None:
        raise PreventUpdate

    state_data = state_data or {}

    return html.Div([
        html.Div('timestamp : {}'.format(ts)),
        html.Div(state_data.get('clicks'))
    ])


# ERASE DATA FROM dcc.Store
@app.callback(Output('store_1', 'clear_data'),
              [Input('erase_stored_data', 'values')])
def delete_counter_data(values):
    if 0 in values:
        return True


# DISPLAY DATA ON APP2 (sencond tab)
@app.callback(Output('display_state_app2', 'children'),
              [Input('tabs', 'value')],
              [State('store_1', 'data')])
def display_stuff_in_app2(value, store_1_data):

    if value == 'app2':

        data1 = store_1_data or {'clicks': 0}

        return html.Div([
            html.Div('store_1_data : {}'.format(data1.get('clicks'))),
        ])

    else:
        return ''


if __name__ == "__main__":
    app.run_server(debug=True, port=8910)

You can take the inspiration from this to build your own app. Basically, the idea would be to store the data from your first dropdown in a dcc.Store component, and then, load what you want in your second dropdown with a callback which have as input “your second tab” and as State, your the data stored in your dcc.Store.

The ressources I used are :

If anyone have another way to do this or any criticism on what I proposed, I would be very interested to hear about

1 Like

Thanks for sharing. I actually was thinking along the same path so thanks for confirming that this should work. This method however requires me to setup the tabs differently: rendering through callbacks as opposed to defining them in the initial layout. I’ll follow up once I get my code refactored.

@jajarbins, what I feared came true, the app is much slower to change tabs. Having a slower startup is a decent trade-off for seamless tab transitions.

Is there any way this can be done using method two of this tutorial, or is that not possible in dash?

I figured out how to synchronize dropdowns while still allowing for all tabs to be rendered at startup! This is a great benefit since it makes tab transitions much smoother, at the cost of a slightly longer initial load. The problem esentially came down to needing to know when a tab was changed. This all changed once I figured out that you can still control the value of tabs regardless of the tabs method you use.

Here is my generalized recipe for those who would like to implement this as well:


# layout portion 

dcc.Store(id='dropdown-cache', data='initial value'),

dcc.Tabs(
    id='tabs',
    value='tab-1',
    parent_className='custom-tabs',
    className='custom-tabs-container',
    children=[

    dcc.Tab(
        label='Tab 1',
        value='tab-1',
        className='custom-tab',
        selected_className='custom-tab--selected',
        children=[
            dcc.Dropdown(
                id='tab-1-dropdown',
            ),
        ]
    ),
    dcc.Tab(
        label='Tab 2',
        value='tab-2',
        className='custom-tab',
        selected_className='custom-tab--selected',
        children=[
            dcc.Dropdown(
                id='tab-2-dropdown',
            ),
        ]
    )
)

# callback portion for synchronizing dropdown across tabs. 

@app.callback(Output('dropdown-cache', 'data'),
              [Input('tab-1-dropdown', 'value'),
               Input('tab-2-dropdown', 'value')],
               [State('tabs', 'value')])
def store_dropdown_cache(tab_1_drodown_sel, tab_2_drodown_sel, tab):
    if tab == 'tab-1':
        return tab_1_drodown_sel
    elif tab == 'tab-2':
        return tab_2_drodown_sel


# Note that using drodowns-cache as an input to change the 
# dropdown value breaks the layout. I feel this has something 
# to do with circular reference, but using the state w/tab 
# value as the input callback trigger works!

@app.callback(Output('tab-1-dropdown', 'value'),
              [Input('tabs', 'value')],
              [State('dropdown-cache', 'data')])
def synchronize_dropdowns(_, cache):
    return cache

@app.callback(Output('tab-2-dropdown', 'value'),
              [Input('tabs', 'value')],
              [State('dropdown-cache', 'data')])
def synchronize_dropdowns(_, cache):
    return cache


Just as a follow up note to the comment above,

The below approach seems more intuitive, but like I mentioned earlier, it won’t work. You can’t have a circular set of callbacks. The dropdown are updating the dcc.Store components, so we can’t have that component’s value change also update the dropdown:


@app.callback(Output('tab_1_drodown', 'value'),
              [Input('dropdown-cache', 'data')])
def synchronize_dropdowns(cache):
    return cache

@app.callback(Output('tab_2_drodown', 'value'),
              [Input('dropdown-cache', 'data')])
def synchronize_dropdowns(cache):
    return cache
5 Likes

This is an awesome recipe, thank you for sharing!
I’m curious if you can point out how to pre-populate the dropdowns?

I’m not sure I understand your question. Could you give me an example.

Thank you for the reply and of course!

For example previously I had two dropdowns with pre-populated values such as below:
dcc.Dropdown(
id=‘dropdown’,
options=[{‘label’: i, ‘value’: i} for i in df[‘Risk_Category’].unique()],
multi=True, value=df[‘Risk_Category’].unique())

In this case the dropdown selections are all possible selections when loading. This worked fine but when I switched to your template the link between tabs worked fine but when first loading the app the dropdown selections are empty. I would like for the link to continue working across tabs but have the dropdowns pre-populated with all possible values when first loading the app.

Hopefully this example makes more sense.

Hmmm, I haven’t had such a use case before, but I’m happy to take a look. Could you create some type of minimal example (you can use dummy hardcoded data instead of df[‘Risk_Category’]) so that I can have something to work with?

On a related note, I’ve been meaning to refactor this recipe as dash has made some nice updates since, namely the callback context and multiple outputs. I think this could make the recipe simpler, and perhaps fix your issue.

Dear maxim,

I am working on something similar. I have an excel file. Now this file is being used to create dash.
There are two tabs- Tab 1 has few graphs and Tab 2 has few tables.
Now I want dropdown, where it filters data from original dataframe. So that this dataframe can be used in both the tabs. On the basis of your code, I can put dropdowns in both tabs.
After your code, where shall I put the contents of the tab to get updated using the filtered data.

It looks like your app1 and app2 layout is in one single file. How would this work if your tab1 layout is in tab1.py and tab2 layout in tab2.py.

I have dcc.Graph and DataTable component in tab1.py and additional ‘dcc.Graph’ components in tab2.py that I’d like to store in memory and sync between tabs.

Basically, I’d like to be able to read from dcc.Store in different files.